Unit test Vuex getters that depend on other getters - javascript

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

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

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

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!

Related

Javascript use decorator to change static class field value?

Is it possible for a JS field decorator to change its value?
A simplified use case would be something like this:
const addItem = (newValue) => {
return function (target) {
target.value.push(newValue);
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]
Using the following experimental decorators:
'#babel/plugin-proposal-decorators',
{
version: '2018-09',
decoratorsBeforeExport: true,
},
End goal is to make a decorator to inject tailwind style sheets into a lit elements static styles. Currently using a mixin for this but just doing this for fun and to learn whats possible with decorators.
Update to Barmars comments
When trying to return a value from the inner function, I end up getting an error:
export const addItem = (value) => {
return function (target) {
return [value];
};
};
Uncaught TypeError: An element descriptor's .kind property must be either "method" or "field", but a decorator created an element descriptor with .kind "undefined"
Looking at the documentation, the variables getting passed to each of these functions doesn't seem to match either.
function logged(value, { kind, name }) {
if (kind === "field") {
return function (initialValue) {
console.log(`initializing ${name} with value ${initialValue}`);
return initialValue;
};
}
}
When running that example, the 2nd parameter to logged() is undefined. "initialValue" also is an object, not the value:
Object { kind: "field", key: "styles", placement: "own", descriptor: {…}, initializer: value(), … }
Nicolo Ribaudo was able to help me over on Babel's discussions. The correct way to do this is to use the initializer function:
const addItem = (newValue) => {
return function (target) {
const { initializer } = target;
target.initializer = function () {
return [
...initializer.call(this),
newValue,
];
};
};
};
class Test {
#addItem(4)
static values = [1,2,3];
}
const test = new Test();
console.log(test.constructor.values) // [1,2,3,4]

Jest Mock returns undefined instead of data

I'm trying to mock a function and not sure what i'm doing wrong here. I have this function "getGroups"
getGroups:
export const getGroups = async () => {
try {
const groupApiUrl = getDashboardPath(GROUPS_TAB_INDEX);
const data = await fetch(groupApiUrl, { cache: 'force-cache' });
const userData = await data.json();
return userData;
} catch (error) {
throw Error(error);
}
};
___mocks___/getGroups.js:
export default async () => {
return {
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
};
};
getGroups.test.js:
jest.mock('./getGroups.js');
// eslint-disable-next-line import/first
import { getGroups } from './getGroups';
const fakeRespose = {
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
};
describe('getGroups', () => {
it('returns data', async () => {
const data = await getGroups();
console.log('DATA', data); <---- UNDEFINED?
expect(data).toBeDefined();
expect(data).toMatchObject(fakeRespose);
});
it('handles error', async () => {
// const data = await getGroups();
await getGroups().toThrow('Failed');
});
});
What are you doing wrong here?
Default export in your mock instead of named as in the implementation
In your implementation you're using named export and you're importing { getGroups } so to make it work you need to change your mock like this
__mocks__\getGroups.js
export const getGroups = async () => {
return {
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
};
};
working example
TL;DR
Testing mock
There is no point at all to test mock function. This does not proves your implementation is working. Even if you change your implementation your tests will still pass.
Use mocks only for the dependencies of your implementation
Use jest.genMockFromModule
It will create jest.fn() for each of the module's exported methods and will preserve the constants, allowing you to change the return value/implementation for some test cases and will also be able to write assertions if the function have been called
__mocks__\getGroups.js
const mock = jest.genMockFromModule('../getGroups');
mock.getGroups.mockResolvedValue({
groups: [
{ id: 1, name: 'Data1' },
{ id: 2, name: 'Data2' }
]
})
module.exports = mock;
Jest will automatically hoist jest.mock calls (read more...)
So you can safely leave the import statements first and then call jest.mock
From Jest Docs, here's an example of a Mock.
jest.mock('../moduleName', () => {
return jest.fn(() => 42);
});
// This runs the function specified as second argument to `jest.mock`.
const moduleName = require('../moduleName');
moduleName(); // Will return '42';
In your case data is undefined, because you haven't actually supplied a mocked implementation for the function or the mock hasn't worked and you're still calling the original function.
Example Reference: https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options
However, in your simple case you could also solve this with a spy, either jest.spyOn or jest.fn(). Here are two solutions to what you're trying to achieve. You can look at the code and run it here: https://repl.it/repls/FairUnsungMice
UPDATE after comment:
Manual mocks are defined by writing a module in a __mocks__/
subdirectory immediately adjacent to the module. For example, to mock
a module called user in the models directory, create a file called
user.js and put it in the models/__mocks__ directory. Note that the
__mocks__ folder is case-sensitive, so naming the directory __MOCKS__ will break on some systems.
Double check the naming, directory structure & type of exports you've setup - they should match. Also, it's worth checking this out: https://github.com/facebook/jest/issues/6127 - looks like an open issue with jest. If you need a solution, look at using a different approach as I mentioned.
Reference: https://jestjs.io/docs/en/manual-mocks

Spying on a non-exported node.js function using jest not working as expected

I am trying to mock a non-exported function via 'jest' and 're-wire'.
Here I am trying to mock 'iAmBatman' (no-pun-intended) but it is not exported.
So I use rewire, which does it job well.
But jest.mock doesn't work as expected.
Am I missing something here or Is there an easy way to achieve the same ?
The error message given by jest is :
Cannot spy the property because it is not a function; undefined given instead
service.js
function iAmBatman() {
return "Its not who I am underneath";
}
function makeACall() {
service.someServiceCall(req => {
iAmBatman();
});
return "response";
}
module.export = {
makeACall : makeACall;
}
jest.js
const services = require('./service');
const rewire = require('rewire');
const app = rewire('./service');
const generateDeepVoice = app.__get__('iAmBatman');
const mockDeepVoice = jest.spyOn(services, generateDeepVoice);
mockDeepVoice.mockImplementation(_ => {
return "But what I do that defines me";
});
describe(`....', () => {
test('....', done => {
services.makeACall(response, () => {
});
});
})
It is not entirely clear what your goal is, but if you look at the documentation of jest.spyOn, you see that it takes a methodName as the second argument, not the method itself:
jest.spyOn(object, methodName)
This explains your error: you didn't give the function name, but the function itself.
In this case, using jest.spyOn(services, 'iAmBatman') wouldn't work, since iAmBatman is not exported, and therefore services.iAmBatman is not defined.
Luckily, you don't need spyOn, as you can simply make a new mock function, and then inject that with rewire's __set__ as follows:
(note that I deleted the undefined service.someServiceCall in your first file, and fixed some typos and redundant imports)
// service.js
function iAmBatman() {
return "Its not who I am underneath";
}
function makeACall() {
return iAmBatman();
}
module.exports = {
makeACall: makeACall
}
// service.test.js
const rewire = require('rewire');
const service = rewire('./service.js');
const mockDeepVoice = jest.fn(() => "But what I do that defines me")
service.__set__('iAmBatman', mockDeepVoice)
describe('service.js', () => {
test('makeACall should call iAmBatman', () => {
service.makeACall();
expect(mockDeepVoice).toHaveBeenCalled();
});
})
Another option would be to restructure your code with iAmBatman in a seperate module, and then mock the module import with Jest. See documentation of jest.mock.

Testing React Components setState overload which takes a function

I am trying to test a React component which uses one of the overloads for setState, but am unsure how to assert the call correctly. An example component would be:
class CounterComponent extends React.Component {
updateCounter() {
this.setState((state) => {
return {
counterValue: state.counterValue + 1
};
});
}
}
The assumption here is that this method will be called asyncronously, so cannot rely on the current state, outwith the call to setState (as it may change before setState executes). Can anyone suggest how you would assert this call? The following test fails as it is simply comparing the function names.
it("Should call setState with the expected parameters", () => {
const component = new CounterComponent();
component.setState = jest.fn(() => {});
component.state = { counterValue: 10 };
component.updateCounter();
const anonymous = (state) => {
return {
counterValue: state.counterValue + 1
};
};
//expect(component.setState).toHaveBeenCalledWith({ counterValue: 11 });
expect(component.setState).toHaveBeenCalledWith(anonymous);
});
Edit: Given yohai's response below, i will add some further context as I feel i may have over simplified the problem however i do not want to re-write the entire question for clarity.
In my actual component, the state value being edited is not a simple number, it is an array of objects with the structure:
{ isSaving: false, hasError: false, errorMessage: ''}
and a few other properties. When the user clicks save, an async action is fired for each item in the array, and then the corresponding entry is updated when that action returns or is rejected. As an example, the save method would look like this:
onSave() {
const { myItems } = this.state;
myItems.forEach(item => {
api.DoStuff(item)
.then(response => this.handleSuccess(response, item))
.catch(error => this.handleError(error, item));
});
}
The handle success and error methods just update the object and call replaceItem:
handleSuccess(response, item) {
const updated = Object.assign({}, item, { hasSaved: true });
this.replaceItem(updated);
}
handleError(error, item) {
const updated = Object.assign({}, item, { hasError: true });
this.replaceItem(updated);
}
And replaceItem then replaces the item in the array:
replaceItem(updatedItem) {
this.setState((state) => {
const { myItems } = state;
const working = [...myItems];
const itemToReplace = working.find(x => x.id == updatedItem.id);
if (itemToReplace) {
working.splice(working.indexOf(itemToReplace), 1, updatedItem);
};
return {
myItems: working
};
});
}
replaceItem is the method I am trying to test, and am trying to validate that it calls setState with the correct overload and a function which correctly updated the state.
My answer below details how I have solved this for myself,but comments and answers are welcome =)
#Vallerii: Testing the resulting state does seem a simpler way, however if i do, there is no way for the test to know that the method is not doing this:
replaceItem(updatedItem) {
const { myItems } = state;
const working = [...myItems];
const itemToReplace = working.find(x => x.id == updatedItem.id);
if (itemToReplace) {
working.splice(working.indexOf(itemToReplace), 1, updatedItem);
};
this.setState({ myItems: working });
}
When replaceItem does not use the correct overload for setState, this code fails when called repeatedly as (I assume) react is batching updates and the state this version uses is stale.
I think you should test something a little bit different and it will look somthing like this (I'm using enzyme):
import React from 'react'
import { mount } from 'enzyme'
import CounterComponent from './CounterComponent'
it("Should increase state by one", () => {
const component = mount(<CounterComponent />)
const counter = 10;
component.setState({ counter });
component.instance().updateCounter();
expect(component.state().counter).toEqual(counter + 1);
});
I have come up with a solution to this after some further thought. I am not sure it is the best solution, but given that the updateCounter method in the example above passes a function into the setState call, I can simply get a reference to that function, execute it with a known state and check the return value is correct.
The resulting test looks like this:
it("Should call setState with the expected parameters", () => {
let updateStateFunction = null;
const component = new CounterComponent();
component.setState = jest.fn((func) => { updateStateFunction = func;});
component.updateCounter();
const originalState = { counterValue: 10 };
const expectedState = { counterValue: 11};
expect(component.setState).toHaveBeenCalled();
expect(updateStateFunction(originalState)).toEqual(expectedState);
});

Mocking globals in Jest

Is there any way in Jest to mock global objects, such as navigator, or Image*? I've pretty much given up on this, and left it up to a series of mockable utility methods. For example:
// Utils.js
export isOnline() {
return navigator.onLine;
}
Testing this tiny function is simple, but crufty and not deterministic at all. I can get 75% of the way there, but this is about as far as I can go:
// Utils.test.js
it('knows if it is online', () => {
const { isOnline } = require('path/to/Utils');
expect(() => isOnline()).not.toThrow();
expect(typeof isOnline()).toBe('boolean');
});
On the other hand, if I am okay with this indirection, I can now access navigator via these utilities:
// Foo.js
import { isOnline } from './Utils';
export default class Foo {
doSomethingOnline() {
if (!isOnline()) throw new Error('Not online');
/* More implementation */
}
}
...and deterministically test like this...
// Foo.test.js
it('throws when offline', () => {
const Utils = require('../services/Utils');
Utils.isOnline = jest.fn(() => isOnline);
const Foo = require('../path/to/Foo').default;
let foo = new Foo();
// User is offline -- should fail
let isOnline = false;
expect(() => foo.doSomethingOnline()).toThrow();
// User is online -- should be okay
isOnline = true;
expect(() => foo.doSomethingOnline()).not.toThrow();
});
Out of all the testing frameworks I've used, Jest feels like the most complete solution, but any time I write awkward code just to make it testable, I feel like my testing tools are letting me down.
Is this the only solution or do I need to add Rewire?
*Don't smirk. Image is fantastic for pinging a remote network resource.
As every test suite run its own environment, you can mock globals by just overwriting them. All global variables can be accessed by the global namespace:
global.navigator = {
onLine: true
}
The overwrite has only effects in your current test and will not effect others. This also a good way to handle Math.random or Date.now.
Note, that through some changes in jsdom it could be possible that you have to mock globals like this:
Object.defineProperty(globalObject, key, { value, writable: true });
The correct way of doing this is to use spyOn. The other answers here, even though they work, don't consider cleanup and pollute the global scope.
// beforeAll
jest
.spyOn(window, 'navigator', 'get')
.mockImplementation(() => { ... })
// afterAll
jest.restoreAllMocks();
Jest may have changed since the accepted answer was written, but Jest does not appear to reset your global after testing. Please see the testcases attached.
https://repl.it/repls/DecentPlushDeals
As far as I know, the only way around this is with an afterEach() or afterAll() to clean up your assignments to global.
let originalGlobal = global;
afterEach(() => {
delete global.x;
})
describe('Scope 1', () => {
it('should assign globals locally', () => {
global.x = "tomato";
expect(global.x).toBeTruthy()
});
});
describe('Scope 2', () => {
it('should not remember globals in subsequent test cases', () => {
expect(global.x).toBeFalsy();
})
});
If someone needs to mock a global with static properties then my example should help:
beforeAll(() => {
global.EventSource = jest.fn(() => ({
readyState: 0,
close: jest.fn()
}))
global.EventSource.CONNECTING = 0
global.EventSource.OPEN = 1
global.EventSource.CLOSED = 2
})
If you are using react-testing-library and you use the cleanup method provided by the library, it will remove all global declarations made in that file once all tests in the file have run. This will then not carry over to any other tests run.
Example:
import { cleanup } from 'react-testing-library'
afterEach(cleanup)
global.getSelection = () => {
}
describe('test', () => {
expect(true).toBeTruthy()
})
If you need to assign and reassign the value of a property on window.navigator then you'll need to:
Declare a non-constant variable
Return it from the global/window object
Change the value of that original variable (by reference)
This will prevent errors when trying to reassign the value on window.navigator because these are mostly read-only.
let mockUserAgent = "";
beforeAll(() => {
Object.defineProperty(global.navigator, "userAgent", {
get() {
return mockUserAgent;
},
});
});
it("returns the newly set attribute", () => {
mockUserAgent = "secret-agent";
expect(window.navigator.userAgent).toEqual("secret-agent");
});

Categories