testing-library / vue-testing-library

🦎 Simple and complete Vue.js testing utilities that encourage good testing practices.
http://testing-library.com/vue
MIT License
1.07k stars 111 forks source link

'[vuex] must call Vue.use(Vuex) before creating a store instance' when changing tests order in src/tests/vuex.js #245

Closed adragich closed 3 years ago

adragich commented 3 years ago

Vue-testing-library Vuex unit test (src/tests/vuex.js) fails if you change tests order or remove ones with renderVuexTestComponent function. It's not a bug of library, but you should refactor your tests as you share these examples in official docs.

To Reproduce Steps to reproduce the behavior:

Run test. Move 'can render with an instantiated Vuex store' test to top or remove test cases with renderVuexTestComponent function. Test fails.

Expected behavior

Test passes.

Screenshot 2021-07-28 at 13 25 35

Related information:

Relevant code or config (if any)

import '@testing-library/jest-dom'
import {render, fireEvent} from '@testing-library/vue'
import Vuex from 'vuex'

import VuexTest from './components/Store/VuexTest'
import {store} from './components/Store/store'

// A common testing pattern is to create a custom renderer for a specific test
// file. This way, common operations such as registering a Vuex store can be
// abstracted out while avoiding sharing mutable state.
//
// Tests should be completely isolated from one another.
// Read this for additional context: https://kentcdodds.com/blog/test-isolation-with-react
function renderVuexTestComponent(customStore) {
  // Render the component and merge the original store and the custom one
  // provided as a parameter. This way, we can alter some behaviors of the
  // initial implementation.
  return render(VuexTest, {store: {...store, ...customStore}})
}

test('can render with an instantiated Vuex store', async () => {
  const {getByTestId, getByText} = render(VuexTest, {
    store: new Vuex.Store({
      state: {count: 3},
      mutations: {
        increment(state) {
          state.count++
        },
        decrement(state) {
          state.count--
        },
      },
      actions: {
        increment(context) {
          context.commit('increment')
        },
        decrement(context) {
          context.commit('decrement')
        },
      },
    }),
  })

  await fireEvent.click(getByText('+'))
  expect(getByTestId('count-value')).toHaveTextContent('4')

  await fireEvent.click(getByText('-'))
  expect(getByTestId('count-value')).toHaveTextContent('3')
})

test('can render with vuex with defaults', async () => {
  const {getByTestId, getByText} = renderVuexTestComponent()
  await fireEvent.click(getByText('+'))

  expect(getByTestId('count-value')).toHaveTextContent('1')
})

test('can render with vuex with custom initial state', async () => {
  const {getByTestId, getByText} = renderVuexTestComponent({
    state: {count: 3},
  })
  await fireEvent.click(getByText('-'))

  expect(getByTestId('count-value')).toHaveTextContent('2')
})

test('can render with vuex with custom store', async () => {
  // This is a silly store that can never be changed.
  // eslint-disable-next-line no-shadow
  const store = {
    state: {count: 1000},
    actions: {
      increment: () => jest.fn(),
      decrement: () => jest.fn(),
    },
  }

  // Notice how here we are not using the helper method, because there's no
  // need to do that.
  const {getByTestId, getByText} = render(VuexTest, {store})

  await fireEvent.click(getByText('+'))
  expect(getByTestId('count-value')).toHaveTextContent('1000')

  await fireEvent.click(getByText('-'))
  expect(getByTestId('count-value')).toHaveTextContent('1000')
})

Additional context

I think you should discourage using instantiated store or show that initializing vuex on global Vue is okay (vuex does it anyways https://github.com/vuejs/vuex/blob/dev/src/store.js#L542).

afontcu commented 3 years ago

Hi! Thank you for the detailed report.

Supporting instantiated Vuex stores was introduced last month in https://github.com/testing-library/vue-testing-library/pull/232. Maybe @blimmer can shed some light on how they are avoiding this error? I have never used this option so I haven't encountered it myself.

blimmer commented 3 years ago

Hey @adragich and @afontcu!

@adragich is correct that, if you're creating an instantiated store, you'll need to call Vue.use(Vuex) somewhere in your code. For my purposes, using jest, I do this once on the global Vue instance in a setupFilesAfterEnv script.

To make this a bit clearer in the tests, I created #247 which explicitly calls .use() on the global Vue object, and also adds a code comment about this behavior. I wasn't sure where I'd add to the documentation, but am happy to make updates if either of you has suggestions. My current thought is that the Vuex error message is pretty good at describing what you need to do to resolve the problem.

afontcu commented 3 years ago

Fixed in #247. Thank you both!