a--m
a--m

Reputation: 4782

Unit test Vuex getters that depend on other getters

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...

Upvotes: 7

Views: 3415

Answers (3)

Daniel Butler
Daniel Butler

Reputation: 3756

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!

Upvotes: 1

a--m
a--m

Reputation: 4782

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'
    ])
  })
})

Upvotes: 2

Kai Sassnowski
Kai Sassnowski

Reputation: 280

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',
}

Upvotes: 4

Related Questions