Vue, vuex: how to unit test a component with namespaced store and mocking?
Asked Answered
E

2

9

I'm building a login component using vue, vuex and vuetify. I've decided to use a namespaced auth module in store and this is causing me the problem.

I'm approaching this using TDD. My e2e test works. But I wrote this unit test (using a mockStore) that should only verify that a proper action has been dispatched:

describe('Login component', () => {
  let wrapper
  const mockStore = {
    dispatch: jest.fn(),
  }

  beforeEach(() => {
    wrapper = mount(Login, {
      localVue,
      mocks: { $store: mockStore },
      computed: {
        error: () => 'test error',
      },
      data: () => ({
        valid: true
      })
    })
  })

  it('should dispatch login action', async () => {
    wrapper.find('[data-test="username"]').setValue('username')
    wrapper.find('[data-test="password"]').setValue('password')
    await wrapper.vm.$nextTick()
    await wrapper.vm.$nextTick()
    wrapper.find('[data-test="login"]').trigger('click')
    await wrapper.vm.$nextTick()
    expect(mockStore.dispatch).toHaveBeenCalledWith(
      `auth/${LOGIN}`,
      { username: 'username', password: 'password' }
    )
  })
})

The component is only using mapActions in the following way:

...mapActions('auth', [LOGIN])

And the button triggering it:

      <v-btn
        color="info"
        @click="login({ username, password })"
        data-test="login"
        :disabled="!valid"
      >Login</v-btn>

The error I'm getting is:

[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'auth/' of undefined"

If I drop the namespace in mapActions, then the dispatched action name I'm getting is not namespaced (duh) and the test fails:

    - Expected
    + Received

    - "auth/login",
    + "login",

I was actually able to fix it by mapping actions like so:

...mapActions({ login: `auth/${LOGIN}` })

But I would really prefer to use namespaced version, because it's gonna get ugly when I have more actions.

I was looking into vuex source code and it fails when trying to access _modulesNamespaceMap but then it gets too complicated for me.

What's the best approach to test this? Should I just give up on mocking and use a real store at this point?

Full project available here and commit relevant to this question is 4a7e749d4

Erratum answered 25/4, 2020 at 19:20 Comment(0)
B
8

Building off of the example on the vue-test-utils docs, I think this should work:

/* ... other imports and setup ... */
import Vuex from 'vuex'

describe('Login component', () => {
  let wrapper
  const actions = {
    login: jest.fn(),
  }
  const mockStore = new Vuex({
    modules: {
      auth: {
        namespaced: true,
        actions,
      },
    },
  })

  beforeEach(() => {
    wrapper = mount(Login, {
      localVue,
      mocks: { $store: mockStore },
      computed: {
        error: () => 'test error',
      },
      data: () => ({
        valid: true
      })
    })
  })

  it('should dispatch login action', async () => {
    wrapper.find('[data-test="username"]').setValue('username')
    wrapper.find('[data-test="password"]').setValue('password')
    await wrapper.vm.$nextTick()
    await wrapper.vm.$nextTick()
    wrapper.find('[data-test="login"]').trigger('click')
    await wrapper.vm.$nextTick()
    expect(actions.login).toHaveBeenCalled() // <-- pretty sure this will work
    expect(actions.login).toHaveBeenCalledWith({ // <-- not as sure about this one
      username: 'username',
      password: 'password',
    })
  })
})
Beedon answered 26/4, 2020 at 1:53 Comment(2)
Great. I ended up using a similar approach and the only thing that bothers me is creating an actual Vuex objectErratum
note for future googlers, I had to use store = new Vuex.Store({... for this to work, not new Vuex({...Attica
D
2

For anybody moving to Vue 3 Test Utils, please note that the createLocalVue method that the above answer relies upon is removed in @vue/test-utils (see https://next.vue-test-utils.vuejs.org/migration/#no-more-createlocalvue).

Instead, it recommends using the createStore method from Vuex. I was able to get namespaced modules working like this:

/* ... other imports and setup ... */
import { mount } from "@vue/test-utils";
import Logon from "path/to/your/logon/component";
import { createStore } from "vuex";

describe('Login component', () => {

  const actions = {
    login: jest.fn(),
  };

  const mockStore = createStore({
    modules: {
      auth: {
        namespaced: true,
        actions,
      },
    },
  });

  let wrapper;

  beforeEach(() => {
    wrapper = mount(Login, {
      global: {
        plugins: [mockStore],
      },
    });
  });

  it('should dispatch login action', async () => {
     /*...test code goes here */
  })
})
Date answered 7/1, 2022 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.