-
-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Testing components that use vuex modules #364
Comments
Not sure if this is the best way, but this is what I'm doing: I have a file that exports a wrapper around // myModule/index.ts
import type { Store } from "vuex";
import { getModule } from "vuex-module-decorators";
import { MyVuexModule } from "./MyVuexModule";
const getMyVuexModule = (store: Store<any>): MyVuexModule => getModule(MyVuexModule, store);
export {
getMyVuexModule,
MyVuexModule
}; Then in my component I use a computed property to get my module: // MyComponent.vue
import { Component, Prop, Vue } from "vue-property-decorator";
import type { MyVuexModule } from "./myModule";
import { getMyVuexModule } from "./myModule";
@Component
export default class MyComponent extends Vue {
public get module(): MyVuexModule {
return getMyVuexModule(this.$store);
}
// ...
} Then in my test file I'm mocking the // MyComponent.spec.ts
import type { MyVuexModule } from "./myModule";
import * as myModule from "./myModule";
import MyComponent from "./MyComponent.vue";
describe("MyComponent", () => {
const mockModule = {
someMutation: jest.fn(),
someAction: jest.fn()
};
beforeEach(() => {
// have to cast as unknown because I'm not mocking the whole module
jest.spyOn(myModule, "getMyVuexModule").mockImplementation(() => mockModule as unknown as MyVuexModule);
});
it("should do something", () => {
// ... |
I was going to go the suggested route of using computed properties to expose module values and testing those. But then I also didnt want to refactor computed properties to expose store values for other computed properties to reflect css conditions. In the end I will end up going the computed route because I found that resetting the data state to be more difficult after mutating modules then just capitulating to unapparent module values via computed properties everywhere. After reflecting on this for a while I suspect there is a way to better organize the root store (store/index.ts) and module (store/module/mobile.ts) such that the actual store property can be mocked here. // App.vue
import { MobileModule } from '@/store/modules/mobile'
import { getModule } from 'vuex-module-decorators'
const mobileModule = getModule(MobileModule)
export default defineComponent({
name: 'App',
computed: {
appClass (): string {
if (mobileModule.isMobileDisplay)
return 'app-mobile'
else
return 'app-full'
}
}
// ...etc
}) my issue is the mobile store is reflecting data in the root store (window listener issuing resize events gets current width captured in the root state's data property) // MobileModule.ts
import { store } from '..'
@Module({
name: 'mobile',
namespaced: true,
preserveState: true,
dynamic: true,
store // <~ this is the item to mock but static imports make this difficult. creator functions would probably help with that.
})
export class MobileModule extends VuexModule {
data: MobileData = { isMobileNavOpen: false }
mobileMaxWidthPx = 768
// ..... other stuff ....
get isMobileDisplay () {
return this.context.rootState.data.innerWidth <= this.mobileMaxWidthPx
}
} Just using a dispatch event on the module's root store to set the value worked, however, at the cost of statefull persistence between tests. After modifying the innerWidth data property to 1024 it persisted across any other test that interacted with it (obviously you would expect this but not great for unit testing). Before/After hooks also seem brittle but may do the trick for some obscure code your targeting and need something quickly without large refactors. // example jest test
it('renders app-full class', () => {
const $store = {
dispatch: jest.fn() // <~ only needed for initial App.vue load due to mounted block and window resize events, wont effect module store above.
}
const mobileModule = getModule(MobileModule)
;((mobileModule as any).store as Store<RootState>).dispatch('setInnerWidth', 1024)
const wrapper = shallowMount(App, { global: { mocks: { $store, ...etc } } })
const classes = wrapper.classes()
expect(classes).toContain('app-full')
}) |
So the way I ended up making these work with my original app layout was though hoisted jest mocks. It depends on jest to execute the mock first before any actual imports (a feature of jest.mock, not jest.doMock). This in turn transform any imports of that module into jest mocks & can be replaced/reset as needed: //app.vue
<script lang="ts">
import { MobileModule } from '@/store/modules/mobile'
const mobileModule = getModule(MobileModule) // dynamic module
export default defineComponent({
name: 'App',
components: {
NavBar,
...
},
computed: {
appClass (): string {
if (mobileModule.isMobileDisplay) {
return 'app-mobile'
} else {
return 'app-full'
}
},
...
}
})
//app.spec.ts
import App from '@/App.vue'
import { MobileModule } from '@/store/modules/mobile'
jest.doMock('@/store', () => {
return {
__esModule: true,
registerModule: function () {
return jest.fn()
},
dispatch: jest.fn(),
getters: jest.fn(),
install: jest.fn(),
replaceState: jest.fn(),
commit: jest.fn(),
subscribe: jest.fn(),
subscribeAction: jest.fn(),
watch: jest.fn(),
unregisterModule: jest.fn(),
hasModule: jest.fn(),
hotUpdate: jest.fn(),
state: {
data: {
innerWidth: 100
}
}
}
})
/**
* The secret sauce is this:
* 1. You need to specify __esModule = true,
* 2. Define _genStatic so this version will be called when invoking `getModule(SomeType)`.
* Remember since `jest.mock` will hoist to the top of the test file, this module is now mocked
* even for your application code.
*
* The `@Module` decorator is never invoked, therefore skipping the `Object.defineProperty` call
* that sets _genStatic normally.
*
* 3. The _vmdModuleName is guarded in `getModuleName` which is invoked
* during `getModule` and is the `name` property you would normally define in the
* `@Module` options object.
*/
jest.mock('@/store/modules/mobile', () => {
return {
__esModule: true,
MobileModule: {
_vmdModuleName: 'mobile',
_genStatic: () => {
return { data: { asdf: '123' }, isMobileDisplay: undefined }
}
}
}
})
describe('App.vue', () => {
beforeEach(() => {
jest.resetModules()
jest.clearAllMocks()
})
it('large screen loads full view', async () => {
const $store = await import('@/store')
const m = MobileModule as MobileModule & any
m._statics.isMobileDisplay = false // set the test data as you normally would
const wrapper = shallowMount(App, {...options, { global: {mocks: $store }})
const classes = wrapper.classes()
expect(classes).toContain('app-full')
})
it('small screen loads mobile view', async () => {
const $store = await import('@/store')
const m = MobileModule as MobileModule & any
m._statics.isMobileDisplay = true // set the test data as you normally would
const wrapper = shallowMount(App, {...options, { global: {mocks: $store }})
const classes = wrapper.classes()
expect(classes).toContain('app-mobile')
})
}) |
could not mock the actions of modules from component in jest.
Please suggest a way to test component that uses vuex modules.
(P.S Vuex modules are made using vuex-module-decorators)
The text was updated successfully, but these errors were encountered: