Unit test Vuex getters that depend on other getters
Asked Answered
D

3

7

I've manage to test Vuex getters that are isolated from other code. I'm now facing some issues when a getter depends on other getters, see the following example:

getters.js

export const getters = {

  getFoo(state) => prefix {
    return `${prefix}: ${state.name}`;
  },

  getFancyNames(state, getters) {
    return [
      getters.getFoo('foo'),
      getters.getFoo('bar')
    ]
  }
}

getters.spec.js

import { getters } = './getters';

const state = {
  name: 'stackoverflow'
};

describe('getFoo', () => {

  it('return name with prefix', () => {
    expect(getters.getFoo(state)('name')).toBe('name: stackoverflow');
  });

});

describe('getFancyNames', () => {

  // mock getters
  const _getters = {
    getFoo: getters.getFoo(state)
  }

  it('returns a collection of fancy names', () => {
    expect(getters.getFancyNames(state, _getters)).toEqual([
      'foo: stackoverflow',
      'bar: stackoverflow'
    ]);
  });
});

When the tested getter depends on other getter that has arguments this means that I've reference the original getter.getFoo on the mock, and this breaks the idea of mocking, since the tests start to have relation with each other. When the getters grow, and the dependency graph has several levels it makes the tests complex.

Maybe this is the way to go, just wanted to check that I'm not missing anything...

Defamatory answered 18/4, 2018 at 20:32 Comment(0)
D
2

Since I'm using Jest there is an option in the jest mock function that let's specify the return value when called:

mockReturnValueOnce or mockReturnValue

More information can be found here: https://facebook.github.io/jest/docs/en/mock-functions.html#mock-return-values

Using the same code as in the question this could be solved like this:

const state = {
  name: 'stackoverflow'
}

describe('getFancyNames', () => {
  const getFoo = jest.fn()
  getFoo.mockReturnValueOnce('foo: stackoverflow')
  getFoo.mockReturnValueOnce('bar: stackoverflow')

  it('returns a collection of fancy names', () => {
    expect(getters.getFancyNames(state, { getFoo })).toEqual([
      'foo: stackoverflow',
      'bar: stackoverflow'
    ])
  })
})
Defamatory answered 29/5, 2018 at 1:5 Comment(0)
L
4

I agree with you that referencing the actual collaborator in your mock defeats the purpose of a mock. So instead I would simply directly return whatever you want your collaborator to return.

In your example, instead of doing something like this:

// mock getters
const _getters = {
  getFoo: getters.getFoo(state)
}

You would simply put in whatever getters.getFoo(state) would return:

const _getters = {
    getFoo: 'foobar' 
}

If you have a getter that takes an additional argument you would simply return a function that returns a constant:

const _getters = {
    getFoo: x => 'foobar',
}
Lumbricoid answered 25/5, 2018 at 10:14 Comment(4)
The problem is that your test will actually run the original getter and then the test won't pass since your mock contains that that generates an output different from the getter output.Defamatory
Not sure I'm following. Your test runs your getter that has the mocked getters as a parameter. I'm actually using this method in a project of mine. Can you give me an example of how that would break your test?Lumbricoid
Oh sorry I think I see it now. Your getFancyNames method calls the same getter twice with different parameters. My example would mean you get back foobar in both cases.Lumbricoid
Thank you for your time. You are right it works, I guess in most cases returning the same value would be the solution, and even the best solution, since you want to isolate your getters tests. I've posted a answer since I found a way to achieve this using jest just in case you feel curious ;)Defamatory
D
2

Since I'm using Jest there is an option in the jest mock function that let's specify the return value when called:

mockReturnValueOnce or mockReturnValue

More information can be found here: https://facebook.github.io/jest/docs/en/mock-functions.html#mock-return-values

Using the same code as in the question this could be solved like this:

const state = {
  name: 'stackoverflow'
}

describe('getFancyNames', () => {
  const getFoo = jest.fn()
  getFoo.mockReturnValueOnce('foo: stackoverflow')
  getFoo.mockReturnValueOnce('bar: stackoverflow')

  it('returns a collection of fancy names', () => {
    expect(getters.getFancyNames(state, { getFoo })).toEqual([
      'foo: stackoverflow',
      'bar: stackoverflow'
    ])
  })
})
Defamatory answered 29/5, 2018 at 1:5 Comment(0)
S
1

A cleaner way that I have found is to create your own mocked getters object. This only works if the getter uses the unaltered state like the question does.

const state = {
  name: 'stackoverflow'
}

describe('getFancyNames', () => {
  const mockedGetters = {
    ...getters,  // This can be skipped
    getFoo: getters.getFoo(state),  // We only overwrite what is needed
  };

  it('returns a collection of fancy names', () => {
    expect(getters.getFancyNames(state, mockedGetters)).toEqual([
      'foo: stackoverflow',
      'bar: stackoverflow'
    ])
  })
})

Extra

In the case that you do need to call other getter functions just pass the mocked getters objects into another mocked getters object. It sounds worse than is actually is.

getters.py

export const getters = {

  getBar(state) = {   // new extra hard part!
    return state.bar,
  },

  getFoo(state, getters) => prefix {
    return `${prefix}: ${state.name} with some ${getters.getBar}`;
  },

  getFancyNames(state, getters) {
    return [
      getters.getFoo('foo'),
      getters.getFoo('bar')
    ]
  }
}
const _mockedGetters = {
  ...getters,  // This can be skipped
  getFoo: getters.getFoo(state),  // We only overwrite what is needed
};

const mockedGetters = {
  .._mockedGetters,  // Use the mocked object!
  getBar: getters.getBar(state, _mockedGetters),  // We only overwrite what is needed
};

// continue down the line as needed!
Stutman answered 26/5, 2021 at 12:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.