Jest testing, call actual implementation on first call, mock the rest - javascript

We are moving a repo from sinon stubs to jest, and I'm having trouble with this mock. What I want to do is call the actual implementation on the first call, then mock the rest of the calls. This function is recursive, so we want the first call to call the actual implementation, then mock the recursive calls.
In sinon, it was done like this
const stub = sandbox.stub(instance, 'function');
stub
.onFirstCall()
.callsFake(stub.wrappedMethod)
.callsFake((args) => args);
I would like to do something like this, but cannot find the actual implementation on the jest spy or mock instance. Is this simply not possible?
const spy = jest.spyOn(instance, 'function');
spy
.mockImplementationOnce(spy.mock.actual) // ???
.mockImplementation((args) => args);

Why can't you do something similar to as follows?
const spy = jest.spyOn(instance, 'function');
spy
.mockImplementationOnce(() => originalInstanceFunction())
.mockImplementation((args) => args);
Here is an example implementation - Note had to store a reference to the original instance function
const original = {
func: (args) => { console.log(`original ${args}`)}
};
describe('test', () => {
it('should call original then mock', () => {
const originalFunction = original.func;
const spy = jest.spyOn(original, 'func');
spy.mockImplementationOnce((args) => originalFunction(args))
.mockImplementation((args) => console.log(`mock ${args}`));
original.func('test-args');
original.func('test-args');
expect(spy).toBeCalledTimes(2);
});
});
Outputs:
console.log
original test-args
at originalFunction (test.test.js:2:28)
console.log
mock test-args
at Object.spy.mockImplementationOnce.mockImplementation.args (test.test.js:12:42)

To do this with a mocked library, use jest.requireActual(). Copying an example from the guide:
jest.mock('node-fetch');
const fetch = jest.requireActual('node-fetch');
This allows you to mock the fetch library in your code being tested, but use the real fetch function in your test itself.

Related

Jest mocks bleeding between tests, reset isn't fixing it

Testing two modules, helper which makes use of render. It's possible for render to throw, so I handle that in helper, and I want tests to ensure that's working as expected.
When I originally wrote the tests, I wrote what was needed for that test in the test itself, including mocks, using jest.doMock. Once all the tests pass I wanted to refactor to share mocks where possible.
So this code works great:
test('throws', async () => {
jest.doMock('./render', () => jest.fn(async () => { throw new Error('mock error'); }));
const helper = require('./helper');
expect(async () => { helper(); }).rejects.toThrow('mock error');
expect(log_bug).toHaveBeenCalled();
});
test('succeeds', async () => {
jest.doMock('./render', () => jest.fn(async () => 'rendered result'));
const helper = require('./helper');
expect(await helper()).toEqual(true); //helper uses rendered result but doesn't return it
expect(log_bug).not.toHaveBeenCalled();
});
HOWEVER, these are not the only two tests and by far most of the other tests that mock render want it to return its success state. I tried to refactor that success use-case out to a file in __mocks__/render.js like so:
// __mocks__/render.js
module.exports = jest.fn(async () => 'rendered result');
And then refactor my tests to this, to be more DRY:
//intention: shared reusable "success" mock for render module
jest.mock('./render');
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
});
test('throws', async () => {
//intention: overwrite the "good" render mock with one that throws
jest.doMock('./render', () => jest.fn(async () => { throw new Error('mock error'); }));
const helper = require('./helper');
expect(async () => { await helper(); }).rejects.toThrow('mock error');
expect(log_bug).toHaveBeenCalled();
});
test('succeeds', async () => {
//intention: go back to using the "good" render mock
const helper = require('./helper');
expect(await helper()).toEqual(true); //helper uses rendered result but doesn't return it
expect(log_bug).not.toHaveBeenCalled();
});
With this updated test code, the error-logging test still works as expected -- the mock is overwritten to cause it to throw -- but then for the next test, the error is thrown again.
If I reverse the order of these tests so that the mock overwriting is last, then the failure doesn't happen, but that is clearly not the correct answer.
What am I doing wrong? Why can't I get my mock to properly reset after overriding it with doMock? The doMock docs do kind of illustrate what I'm trying to do, but they don't show mixing it with normal manual mocks.
Aha! I kept digging around and found this somewhat similar Q+A, which led me to try this approach instead of using jest.doMock to override inside of a test:
//for this one test, overwrite the default mock to throw instead of succeed
const render = require('./render');
render.mockImplementation(async () => {
throw new Error('mock error');
});
And with this, the tests pass no matter what order they run!

Jest mock a module to produce different results on function calls

I have a module:
// foo.js
module.exports = async () => {
...
}
This module is called in another module, which behaviour I'm testing:
// bar.js
const one = await foo();
const two = await foo();
I want to mock foo with Jest, so that multiple calls on it return different results. More precisely, the first call to be successful, the second one to return an error.
This is my mocking mechanism:
const fooMock = jest.mock('../src/foo')
fooMock
.mockImplementationOnce(() => Promise.resolve({ id: 'asdf' }))
.mockImplementationOnce(() => Promise.reject(new Error('some error')))
The problem is that mockImplementationOnce is not a function of jest.mock(). It's only a function of jest.fn(). The jest.mock() object only has mockImplementation which will mock and seal the return result of the mocked function and doesn't allow for different results on multiple calls.
How can I mock the module to return different results on 1st and on 2nd call?
Inspiration taken from the jest docs here.
UPDATE:
I also tried this approach:
jest.mock('../src/foo', () => jest.fn()
.mockImplementationOnce(() => Promise.resolve({ _id: 'asdf' }))
.mockImplementationOnce(() => Promise.reject('some error'))
)
But now no mocking is happening at all.
You should use mockFn.mockReturnValueOnce(value):
Accepts a value that will be returned for one call to the mock function. Can be chained so that successive calls to the mock function return different values
After calling jest.mock('./src/foo'), you should import the ./src/foo module and it will be a mocked version instead of using the return value.
const fooMock = require('./src/foo');
jest.mock('./src/foo');
test('should pass', () => {
fooMock.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call')
// 'first call', 'second call', 'default', 'default'
console.log(fooMock(), fooMock(), fooMock(), fooMock());
})

How would I test this promise based code with jest?

How would I test this code in jest? I'd like to make sure that the error and success of the passed promise is being called as needed. I'm sure it's something sorta simple, but it's driving me crazy. Thanks very much.
handleStatusChangeRequest (changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
If your code uses promises, there is a nice way to handle asynchronous tests. Just return a promise from your test, and Jest will wait for that promise to resolve.
If the promise is rejected, the test will automatically fail.
For example, let's say that changeData, instead of using a callback, returns a promise that is supposed to resolve to the string "status has been successfully modified".
Be sure to return the promise - if you omit this return statement, your test will complete before your changeData() -[async function] completes.
Here's a convenient and easy to follow pattern
test('if the data is changed', () => {
return changeData().then((data) => {
expect(data).toBe('status has been successfully modified');
});
})
Happy testing :)
This could be refactored, but for the sake of demonstration, I left the repeating bits in.
In example.spec.js, the callback, changeEntryStatus, is stubbed to return a promise. In order to check if other instance methods (this.method) were called, they are first mocked, then assertions are called on the mock after running the method being tested. Learn more in the Jest docs. (See my thoughts on mocking methods of the unit being tested at the bottom.)
Run the example on repl.it.
example.js:
class Example {
handleStatusChangeRequest(changeEntryStatus) {
return changeEntryStatus().then(() => {
this.handleStatusChangeSuccess()
}).catch(err => {
this.handleErrorDisplay(err)
})
}
handleStatusChangeSuccess() {
console.log('stubbed handleStatusChangeSuccess')
}
handleErrorDisplay(error) {
console.log('stubbed handleErrorDisplay:', error)
}
}
module.exports = Example;
example.spec.js:
const Example = require('./entryStatus')
describe('handleStatusChangeRequest', () => {
it('should run the changeEntryStatus callback', () => {
const {handleStatusChangeRequest} = new Example()
const stub = jest.fn().mockResolvedValue()
handleStatusChangeRequest(stub)
// must return because handleStatusChangeRequest is asynchronous
return expect(stub).toHaveBeenCalled()
});
it('should call example.handleStatusChangeSuccess', async () => {
const example = new Example()
const stub = jest.fn().mockResolvedValue()
example.handleStatusChangeSuccess = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleStatusChangeSuccess).toHaveBeenCalled();
})
it('should call example.handleErrorDisplay', async () => {
const example = new Example()
const fakeError = { code: 'fake_error_code' }
const stub = jest.fn().mockRejectedValue(fakeError)
example.handleErrorDisplay = jest.fn()
await example.handleStatusChangeRequest(stub)
expect(example.handleErrorDisplay).toHaveBeenCalled()
expect(example.handleErrorDisplay).toHaveBeenCalledWith(fakeError)
});
});
Opinionated Disclaimer: Mocking methods of the unit under test is a smell. Consider checking for the expected effects of calling handleStatusChangeSuccess and handleErrorDisplay instead of checking to see if they were called. Then don't even expose those methods publicly unless consumers of the class need access.
Opinionated Disclaimer: Mocking methods of the unit under test is a
smell. Consider checking for the expected effects of calling
handleStatusChangeSuccess and handleErrorDisplay instead of checking
to see if they were called. Then don't even expose those methods
publicly unless consumers of the class need access.
I wholeheartedly agree with webprojohn's disclaimer. Mocks are a smell as tests should assert the behavior of the code, not its implementation. Testing the latter makes the code brittle to change.
Stepping off my soapbox... :) We're looking for a way to test an asynchronous method. I'm not sure what assertions your tests should make to verify the behavior inside handleStatusChangeSuccess() and handleErrorDisplay(err) so the example below leaves a comment where those assertions would go. The following uses Promise.resolve() and Promise.reject() to trigger the outcomes to test. I've used async/await, Jest has other async examples in their docs.
const Example = require('./example')
describe('handleStatusChangeRequest', () => {
it('should resolve successfully', async () => {
const {handleStatusChangeRequest} = new Example();
const resolvePromise = () => Promise.resolve();
await handleStatusChangeRequest(resolvePromise);
// resolution assertions here
});
it('should resolve errors', async () => {
const {handleStatusChangeRequest} = new Example();
const fakeError = new Error('eep');
const rejectPromise = () => Promise.reject(fakeError);
// if your method doesn't throw, we can remove this try/catch
// block and the fail() polyfill
try {
await example.handleStatusChangeRequest(rejectPromise);
// if we don't throw our test shouldn't get here, so we
// polyfill a fail() method since Jest doesn't give us one.
// See https://github.com/facebook/jest/issues/2129
expect(true).toBe(false);
}
catch (e) {
// rejection assertions here
}
});
});
The answer I have looks so:
**Success tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleStatusChangeSuccess')
await instance.handleStatusChangeRequest(() => Promise.resolve('cool man'))
expect(spy).toHaveBeenCalledTimes(1)
**Error tests
const instance = el.find(EntryToolBar).instance()
const spy = jest.spyOn(instance, 'handleErrorDisplay')
await instance.handleStatusChangeRequest(() => Promise.reject(Error('shit')))
expect(spy).toHaveBeenCalledTimes(1)
As I stated above, the handleStatusChangeSuccess and handleError methods are test else where with some snapshots (they just set state and render out some different jsx). I feel pretty good about this. I'm using spys/mocks, but I'm testing the implementation functions elsewhere. Sufficient?

Which is Jest way for restoring mocked function

In Sinon's stub it is very easy to restore functionality.
const stub = sinon.stub(fs,"writeFile",()=>{})
...
fs.writeFile.restore()
I am looking to do the same thing with Jest. The closest I get is this ugly code:
const fsWriteFileHolder = fs.writeFile
fs.writeFile = jest.fn()
...
fs.writeFile = fsWriteFileHolder
Finally I found a workable solution thanks to #nbkhope's contribution.
So the following code work as expected, i.e. it mocks the code and then it restore the original behavior:
const spy = jest.spyOn(
fs,
'writeFile'
).mockImplementation((filePath,data) => {
...
})
...
spy.mockRestore()
If you want to clear all the calls to the mock function, you can use:
const myMock = jest.fn();
// ...
myMock.mockClear();
To clear everything stored in the mock, try instead:
myMock.mockReset();
jest.spyOn() is helpful when mocking and unmocking methods, however there might be a situation where you want to mock and unmock esModule.
I had such situation recently and I found this solution to work for me:
// Module that will be mocked and unmocked
import exampleModule from 'modules/exampleModule';
const ActualExampleModule = jest.requireActual('modules/exampleModule');
describe('Some tests that require mocked module', () => {
// Tests on mock
});
describe('Some tests that require original module', () => {
it('Test with restored module', async () => {
const restoredModule = await import('modules/exampleModule');
restoredModule.default = ActualExampleModule .default;
// Now we can assert on original module
});
});

spying on functions returned by a function sinon

I'm a bit new to Sinon and having some trouble with the scenario where I need to spy on not only a function, but the functions returned by the function. Specifically, I'm trying to mock the Azure Storage SDK and ensure that once I've created a queue service, that the methods returned by the queue service are also called. Here's the example:
// test.js
// Setup a Sinon sandbox for each test
test.beforeEach(async t => {
sandbox = Sinon.sandbox.create();
});
// Restore the sandbox after each test
test.afterEach(async t => {
sandbox.restore();
});
test.only('creates a message in the queue for the payment summary email', async t => {
// Create a spy on the mock
const queueServiceSpy = sandbox.spy(AzureStorageMock, 'createQueueService');
// Replace the library with the mock
const EmailUtil = Proxyquire('../../lib/email', {
'azure-storage': AzureStorageMock,
'#noCallThru': true
});
await EmailUtil.sendBusinessPaymentSummary();
// Expect that the `createQueueService` method was called
t.true(queueServiceSpy.calledOnce); // true
// Expect that the `createMessage` method returned by
// `createQueueService` is called
t.true(queueServiceSpy.createMessage.calledOnce); // undefined
});
Here's the mock:
const Sinon = require('sinon');
module.exports = {
createQueueService: () => {
return {
createQueueIfNotExists: (queueName) => {
return Promise.resolve(Sinon.spy());
},
createMessage: (queueName, message) => {
return Promise.resolve(Sinon.spy());
}
};
}
};
I'm able to confirm that the queueServiceSpy is called once, but I'm having trouble determining if the methods returned by that method are called (createMessage).
Is there a better way to set this up or am I just missing something?
Thanks!
What you need to do is stub your service function to return a spy that you can then track calls to elsewhere. You can nest this arbitrarily deep (though I would strongly discourage that).
Something like:
const cb = sandbox.spy();
const queueServiceSpy = sandbox.stub(AzureStorageMock, 'createQueueService')
.returns({createMessage() {return cb;}}});
const EmailUtil = Proxyquire('../../lib/email', {
'azure-storage': AzureStorageMock,
'#noCallThru': true
});
await EmailUtil.sendBusinessPaymentSummary();
// Expect that the `createQueueService` method was called
t.true(queueServiceSpy.calledOnce); // true
// Expect that the `createMessage` method returned by
// `createQueueService` is called
t.true(cb.calledOnce);

Categories

Resources