Is there a shorter way to spy on object methods than the following?
describe('blah blah', () => {
let localStorageSetSpy, localStorageGetSpy;
beforeEach(() => {
localStorageGetSpy = spyOn(window.localStorage, 'getItem');
localStorageSetSpy = spyOn(window.localStorage, 'setItem');
});
it('yada yada', () => {
// blah blah
})
})
It would be nice if I could spy on the getter and setter as part of the same object. I tried reassigning window.localStorage to jasmine.createSpyObj(...) but got complaints about it being readonly.
Any ideas?
If you just want to interact with one object in your test, maybe you can do this
describe('blah blah', () => {
const localStorageSpy = { getItem: undefined, setItem: undefined };
beforeEach(() => {
localStorageSpy.getItem = spyOn(window.localStorage, 'getItem');
localStorageSpy.setItem = spyOn(window.localStorage, 'setItem');
});
it('yada yada', () => {
localStorageSpy.getItem.mockReturnValue(...)
})
})
I would use some methods or functions to wrap around your window.localStorage operations. For example store() (which calls getItem) and retrieve() (which calls setItem).
Some benefits:
You will be able to spy on and mock said functions very easily.
In the future, you will be able to change the behaviour of these functions without having to care about what is done inside as long as the prototypes of store() and retrieve() stay the same.
describe('blah blah', () => {
const localStorageSpy = jasmine.createSpyObj('localStorage',['getItem','setItem']) // Jasmine automatically create a spy for you
beforeEach(() => {
});
it('yada yada', () => {
localStorageSpy.getItem = (() => { return mockReturnValue(...) }) // Do your test here.
})
})
Maybe you can try this. Jasmine createSpyObj will create spy for as well. However, for localStorage it is good that you come out with a mock implmentation.
Related
Currently I have this:
jest.mock('my/hook', () => () => false)
I want my custom React hook module to return false in every test by default, but in a few tests I want it to return true.
The hook is implemented essentially like this:
function useMyHook(key) {
switch (key) {
case 'foo':
case 'bar':
return true
default:
return false
}
}
I am using the hook several times in my component, once for the foo key and once for the bar key. I want it to return false for both keys by default.
But for a few tests I want the foo key to return true, and for other tests I want the bar key to return true.
I tried that by doing this in the specific test, but it didn't do anything:
it('should do x', () => {
jest.doMock('my/hook', () => (key) => {
if (key == 'foo') {
return true
}
})
// ... rest of test
})
How do I customize module mocks on a per-test basis in Jest?
jest.doMock alone can't do anything because a module that depends on it has been already imported earlier. It should be re-imported after that, with module cache discarded with either jest.resetModules or jest.isolateModules:
beforeEach(() => {
jest.resetModules();
});
it('should do x', () => {
jest.doMock('my/hook', ...)
require('module that depends on hook');
// ... rest of test
})
Since it's a function that needs to be mocked differently, a better way is to mock the implementation with Jest spies instead of plain functions:
jest.mock('my/hook', () => jest.fn(() => false))
...
it('should do x', () => {
hook.mockReturnValueOnce(true);
// ... rest of test
})
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.
This is a follow up question to this question.
I have a function called assertTruthy for Jest. assertTruthy(msg, fn, args), expects a message, a function and arguments and should pass if the thing that is returned when the function is invoked with the arguments is truthy and fail if its not.
I want to extend it to also support Jest's only and skip.
Here is what I wrote:
assertTruthy.skip = ({
message = '',
fn = undefined,
args,
} = {}) => {
it.skip(message, () => {
expect(fn(args)).toBeTruthy();
});
};
assertTruthy.only = ({
message = '',
fn = undefined,
args,
} = {}) => {
it.only(message, () => {
expect(fn(args)).toBeTruthy();
});
};
How would I test these functions?
Here is what I tried, which works, but I'm not sure if this is correct.
describe('skip()', () => {
test('it skips the function', () => {
it.skip = jest.fn();
assertTruthy.skip({
message: 'something',
fn: () => true,
args: undefined,
});
expect(it.skip).toHaveBeenCalledTimes(1);
});
});
This looks like a fair enough test that your assertTruthy skip and only call Jest's it skip and only methods.
You might want to assert that it also calls them with the arguments you expect using toHaveBeenCalledWith.
Can you please elaborate more, what you want to achieve and I did't get it properly why you want to extend jest skip and only methods to achieve the same thing that you are already testing.
But if you only want to test if a function not been invoked/executed with toHaveBeenCalledTimes() based on the arguments to be truthy/falsy then you are doing it right.
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!
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");
});