"[vuex] state field foo was overridden by a module with the same name at foobar" with deepmerge helper function in jest
I believe the warning is somewhat misleading in this case. It is technically true, just not helpful.
The following code will generate the same warning. It doesn't use deepmerge
, vue-test-utils
or jest
but I believe the root cause is the same as in the original question:
const config = {
state: {},
modules: {
customer: {}
}
}
const store1 = new Vuex.Store(config)
const store2 = new Vuex.Store(config)
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
There are two key parts of this example that are required to trigger the warning:
- Multiple stores.
- A root
state
object in the config.
The code in the question definitely has multiple stores. One is created at the end of store.js
and the other is created by createStore
.
The question doesn't show a root state
object, but it does mention that the code has been reduced. I'm assuming that the full code does have this object.
So why does this trigger that warning?
Module state
is stored within the root state
object. Even though the module in my example doesn't explicitly have any state
it does still exist. This state
will be stored at state.customer
. So when the first store gets created it adds a customer
property to that root state
object.
So far there's no problem.
However, when the second store gets created it uses the same root state
object. Making a copy or merging the config at this stage won't help because the copied state
will also have the customer
property. The second store also tries to add customer
to the root state
. However, it finds that the property already exists, gets confused and logs a warning.
There is some coverage of this in the official documentation:
https://vuex.vuejs.org/guide/modules.html#module-reuse
The easiest way to fix this is to use a function for the root state
instead:
state: () => ({ /* all the state you currently have */ }),
Each store will call that function and get its own copy of the state. It's just the same as using a data
function for a component.
If you don't actually need root state
you could also fix it by just removing it altogether. If no state
is specified then Vuex will create a new root state
object each time.
It is logged when a property name within the state conflicts with the name of a module, like so:
new Vuex.Store({
state: {
foo: 'bar'
},
modules: {
foo: {}
}
})
therefore this raises the warning.
new Vuex.Store(({
state: {
cart: '',
customer: '',
checkout: ''
},
modules: {
cart: {},
customer: {},
checkout: {},
}
}))
its most likely here
export const createStore = config => {
const combinedConfig = (config)
? merge(storeConfig, config)
: storeConfig
return new Vuex.Store(combinedConfig)
}
from the source code of vuex, it helps indicate where these errors are being raised for logging.
If you run the app in production, you know that this warning wont be raised... or you could potentially intercept the warning and immediately return;
vuex source code
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.state)
})
vuex tests
jest.spyOn(console, 'warn').mockImplementation()
const store = new Vuex.Store({
modules: {
foo: {
state () {
return { value: 1 }
},
modules: {
value: {
state: () => 2
}
}
}
}
})
expect(store.state.foo.value).toBe(2)
expect(console.warn).toHaveBeenCalledWith(
`[vuex] state field "value" was overridden by a module with the same name at "foo.value"`
)
Well, I believe there is no need for using deepmerge
in test-utils.ts. It is better we use Vuex itself to handle the merging of the module instead of merging it with other methods.
If you see the documentation for Vuex testing with Jest on mocking modules
you need to pass the module which is required.
import { createStore, createLocalVue } from 'test-utils';
import Vuex from 'vuex';
const localVue = createLocalVue()
localVue.use(Vuex);
// mock the store in beforeEach
describe('MyComponent.vue', () => {
let actions
let state
let store
beforeEach(() => {
state = {
availableShippingMethods: {
flatrate: {
carrier_title: 'Flat Rate',
},
},
}
actions = {
moduleActionClick: jest.fn()
}
store = new Vuex.Store({
modules: {
checkout: {
state,
actions,
getters: myModule.getters // you can get your getters from store. No need to mock those
}
}
})
})
});
whistle, In test cases:
const wrapper = shallowMount(MyComponent, { store, localVue })
Hope this helps!