Trigger mocked callback argument with Jest - javascript

Using Jest, I'd like to trigger the configure callback argument. If I were to write this with a sinon stub, I could do something like configure.yields('my value'). Does Jest have anything similar? To help illustrate what I'm after, I created a simple example.
I've imported mymodule, instantiated, and called the configure function. I'd like my test to trigger this callback. i.e. (err, results) => ...
import MyModule from 'mymodule';
export function execute(key, value) {
return new Promise((resolve, reject) => {
new MyModule().configure(key, value, (err, result) => {
// I need my test to trigger this section of code...
return resolve('my resolved value')
})
})
}
This test is mocking MyModule and setting configure to a jest.fn(). While I have everything mocked, I'm not able to specify the configure args and trigger a specific argument. Ideally, I'd like to do something like mockFn.yields('my value') to trigger the configure callback.
import MyModule from 'mymodule';
jest.mock('mymodule');
describe('Test Example', () => {
test('should trigger mocked args callback', async () => {
const mockFn = jest.fn();
const key = 'my_key';
const value = 'my_value';
const actual = require('./src/my-service');
MyModule.mockImplementation(() => {
return {
configure: mockFn
};
});
await actual.execute(key, value)
// how can I trigger the mocked configure argument callback?
});
});
Test currently fails because it cannot trigger the callback function. Error states: Async callback was not invoked

The error means that a promise that test async function returns wasn't settled. This happens because execute returns a pending promise, mocked configure doesn't call a callback that is supposed to resolve it.
configure should be mocked correctly and behave the same way as original implementation regarding callback argument:
mockFn.mockImplementation((key, value, cb) => cb(null, 'some result'));
const promise = actual.execute(key, value);
expect(mockFn).toBeCalledWith(key, value, expect.any(Function));
await expect(promise).resolves.toBe('my resolved value');

Related

How to mock const method in jest?

I unit test code in typescript, use jest. Please teach me how to mock getData to return the expected value. My code as below:
// File util.ts
export const getData = async () => {
// Todo something
return data;
}
// File execution.ts import { getData } from './util';
function execute()
{
// todo something
const data = await getData();
// todo something
}
The problem is that your function returns a promise. Depends on how you use it there are several ways to mock it.
The simplest way would be to mock it directly, but then it will always return the same value:
// note, the path is relative to your test file
jest.mock('./util', () => ({ getData: () => 'someValue' }));
If you want to test both the resolved and the rejected case you need to mock getData so it will return a spy where you later on can change the implementation use mockImplementation. You also need to use async/await to make the test work, have a look at the docs about asynchronous testing:
import { getData } from './util';
jest.mock('./util', () => ({ getData: ()=> jest.fn() }));
it('success case', async () => {
const result = Promise.resolve('someValue');
getData.mockImplementation(() => result);
// call your function to test
await result; // you need to use await to make jest aware of the promise
});
it('error case', async () => {
const result = Promise.reject(new Error('someError'));
getData.mockImplementation(() => result);
// call your function to test
await expect(result).rejects.toThrow('someError');
});
Try the following in your test file.
Import the function from the module.
import { getData } from './util';
Then mock the module with the function and its return value after all the import statements
jest.mock('./util', () => ({ getData: jest.fn() }))
getData.mockReturnValue("abc");
Then use it in your tests.
Because mocking expression functions can be a real pain to get right, I'm posting a full example below.
Scenario
Let's say we want to test some code that performs some REST call, but we don't want the actual REST call to be made:
// doWithApi.ts
export const doSomethingWithRest = () => {
post("some-url", 123);
}
Where the post is a function expression in a separate file:
// apiHelpers.ts
export const post = (url: string, num: number) => {
throw Error("I'm a REST call that should not run during unit tests!");
}
Setup
Since the post function is used directly (and not passed in as a parameter), we must create a mock file that Jest can use during tests as a replacement for the real post function:
// __mocks__/apiHelpers.ts
export const post = jest.fn();
Spy and Test
Now, finally inside the actual test, we may do the following:
// mockAndSpyInternals.test.ts
import {doSomethingWithRest} from "./doWithApi";
afterEach(jest.clearAllMocks); // Resets the spy between tests
jest.mock("./apiHelpers"); // Replaces runtime functions inside 'apiHelpers' with those found inside __mocks__. Path is relative to current file. Note that we reference the file we want to replace, not the mock we replace it with.
test("When doSomethingWithRest is called, a REST call is performed.", () => {
// If we want to spy on the post method to perform assertions we must add the following lines.
// If no spy is wanted, these lines can be omitted.
const apiHelpers = require("./apiHelpers");
const postSpy = jest.spyOn(apiHelpers, "post");
// Alter the spy if desired (e.g by mocking a resolved promise)
// postSpy.mockImplementation(() => Promise.resolve({..some object}))
doSomethingWithRest();
expect(postSpy).toBeCalledTimes(1)
expect(postSpy).toHaveBeenCalledWith("some-url", 123);
});
Examples are made using Jest 24.9.0 and Typescript 3.7.4

Is there a way to pass parameters to a mock function?

using jest to unit test, I have the following line:
jest.mock('../../requestBuilder');
and in my folder, i have a
__mocks__
subfolder where my mock requestBuilder.js is. My jest unit test correctly calls my mock requestBuilder.js correctly. Issue is, my requestBuilder is mocking an ajax return, so I want to be able to determine if I should pass back either a successful or failure server response. Ideally I want to pass a parameter into my mock function to determine if "ajaxSuccess: true/false". How can I do this? Thank you
You don't want to pass a parameter into your mock function, the parameters that are passed to your mock function should be controlled by the piece of code that you are testing. What you want to do is change the mocking behavior between executions of the mock function.
Let's assume that you're trying to test this snippet of code:
// getStatus.js
const requestBuilder = require('./requestBuilder');
module.exports = () => {
try {
const req = requestBuilder('http://fake.com/status').build();
if (req.ajaxSuccess) {
return {status: 'success'};
} else {
return {status: 'failure'}
}
} catch (e) {
return {status: 'unknown'};
}
};
We want to test that getStatus uses the requestBuilder properly, not that the builder.build() method works correctly. Verifying builder.build() is the responsibility of a separate unit test. So we create a mock for our requestBuilder as follows:
// __mocks__/requestBuilder.js
module.exports = jest.fn();
This mock simply sets up the mock function, but it does not implement the behavior. The behavior of the mock should defined in the test. This will give you find grained control of the mocking behavior on a test-by-test basis, rather than attempting to implement a mock that supports every use case (e.g. some special parameter that controls the mocking behavior).
Let's implement some tests using this new mock:
// getStatus.spec.js
jest.mock('./requestBuilder');
const requestBuilder = require('./requestBuilder');
const getStatus = require('./getStatus');
describe('get status', () => {
// Set up a mock builder before each test is run
let builder;
beforeEach(() => {
builder = {
addParam: jest.fn(),
build: jest.fn()
};
requestBuilder.mockReturnValue(builder);
});
// every code path for get status calls request builder with a hard coded URL,
// lets create an assertion for this method call that runs after each test execution.
afterEach(() => {
expect(requestBuilder).toHaveBeenCalledWith('http://fake.com/status');
});
it('when request builder creation throws error', () => {
// Override the mocking behavior to throw an error
requestBuilder.mockImplementation(() => {
throw new Error('create error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).not.toHaveBeenCalled();
});
it('when build throws an error', () => {
// Set the mocking behavior to throw an error
builder.build.mockImplementation(() => {
throw new Error('build error')
});
expect(getStatus()).toEqual({status: 'unknown'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns success', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: true});
expect(getStatus()).toEqual({status: 'success'});
expect(builder.build).toHaveBeenCalled();
});
it('when request builder returns failure', () => {
// Set the mocking behavior to return ajaxSuccess value
builder.build.mockReturnValue({ajaxSuccess: false});
expect(getStatus()).toEqual({status: 'failure'});
expect(builder.build).toHaveBeenCalled();
});
});

Jest Unit test, mock implementation of IF condition within function for complete code coverage

I have a API script in a file
const ApiCall = {
fetchData: async (url) => {
const result = await fetch(url);
if (!result.ok) {
const body = await result.text(); // uncovered line
throw new Error(`Error fetching ${url}: ${result.status} ${result.statusText} - ${body}`); // uncovered line
}
return result.json();
},
};
export default ApiCall;
When I mock the call, I have two uncovered lines in code coverage.
Any idea how can I make them cover as well.
Here is what I have tried so far which is not working
it('test', async () => {
ApiCall.fetchData = jest.fn();
ApiCall.fetchData.result = { ok: false };
});
I am kind of new into Jest, so any help would be great.
You need to provide a stubb response in your test spec so that the if statement is triggered. https://www.npmjs.com/package/jest-fetch-mock will allow you to do just that. The example on their npm page should give you what you need https://www.npmjs.com/package/jest-fetch-mock#example-1---mocking-all-fetches
Basically the result is stored in state(redux) and is called from there. jest-fetch-mock overrides your api call/route and returns the stored result in redux all within the framework.
Assuming that what you want to test is the ApiCall then you would need to mock fetch. You are mocking the entire ApiCall so those lines will never execute.
Also, you have an issue, because if you find an error or promise rejection, the json() won't be available so that line will trigger an error.
Try this (haven't test it):
it('test error', (done) => {
let promise = Promise.reject(new Error("test"));
global.fetch = jest.fn(() => promise); //You might need to store the original fetch before swapping this
ApiCall.fetchData()
.catch(err => );
expect(err.message).toEqual("test");
done();
});
it('test OK', (done) => {
let promise = Promise.resolve({
json: jest.fn(() => {data: "data"})
});
global.fetch = jest.fn(() => promise);
ApiCall.fetchData()
.then(response => );
expect(response.data).toEqual("data");
done();
});
That probably won't work right away but hopefully you will get the idea. In this case, you already are working with a promise so see that I added the done() callback in the test, so you can tell jest you finished processing. There is another way to also make jest wait for the promise which is something like "return promise.then()".
Plese post back

React and jest mock module

I am creating an application in which I use redux and node-fetch for remote data fetching.
I want to test the fact that I am well calling the fetch function with a good parameter.
This way, I am using jest.mock and jasmine.createSpy methods :
it('should have called the fetch method with URL constant', () => {
const spy = jasmine.createSpy('nodeFetch');
spy.and.callFake(() => new Promise(resolve => resolve('null')));
const mock = jest.mock('node-fetch', spy);
const slug = 'slug';
actionHandler[FETCH_REMOTE](slug);
expect(spy).toHaveBeenCalledWith(Constants.URL + slug);
});
Here's the function that I m trying to test :
[FETCH_REMOTE]: slug => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
await fetch(Constants.URL + slug);
addLocal();
};
}
AS you can see, I am trying to log the console.log(fetch()) behavior, and I am having the default promise to resolve given by node-fetch, and not the that I've mock with Jest and spied with jasmine.
Do you have an idea what it doesn't work ?
EDIT : My test displayed me an error like my spy has never been called
Your action-handler is actually a action handler factory. In actionHandler[FETCH_REMOTE], you are creating a new function. The returned function taskes dispatch as a parameter and invokes the code you are showing.
This means that your test code will never call any function on the spy, as the created function is never invoked.
I think you will need to create a mock dispatch function and do something like this:
let dispatchMock = jest.fn(); // create a mock function
actionHandler[FETCH_REMOTE](slug)(dispatchMock);
EDIT:
To me, your actionHandler looks more like an actionCreator, as it is usually called in redux terms, though I personally prefer to call them actionFactories because that is what they are: Factories that create actions.
As you are using thunks(?) your actionCreater (which is misleadingly named actionHandler) does not directly create an action but another function which is invoked as soon as the action is dispatched. For comparison, a regular actionCreator looks like this:
updateFilter: (filter) => ({type: actionNames.UPDATE_FILTER, payload: {filter: filter}}),
A actionHandler on the other hand reacts to actions being dispatched and evaluates their payload.
Here is what I would do in your case:
Create a new object called actionFactories like this:
const actionFactories = {
fetchRemote(slug): (slug) => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
let response = await fetch(Constants.URL + slug);
var responseAction;
if (/* determine success of response */) {
responseAction = actionFactories.fetchSuccessful(response);
} else {
responseAction = actionFactories.fetchFailed();
}
dispatch(responseAction);
};
}
fetchFailed(): () => ({type: FETCH_FAILED, }),
fetchSuccessful(response): () => ({type: FETCH_FAILED, payload: response })
};
Create an actionHandler for FETCH_FAILED and FETCH_SUCCESSFUL to update the store based on the response.
BTW: Your console.log statement does not make much sense too me, since fetch just returns a promise.

Chai spies and async calls not call once

I m building an application in which I need to test some callback behaviours inside of an express callback resolution.
Actually, it looks like :
const callbackRender = (httpResponse, response) => {
console.log(httpResponse) // logs the good httpResponse object
if (httpResponse.content.content) response.send(httpResponse.content.content)
else response.render(httpResponse.content.page)
}
const callback = (injector, route) => {
return (request, response) => {
const ctrl = injector.get(route.controller)
const result = ctrl[route.controllerMethod](new HttpRequest())
if (result.then) {
return result.then(res => callbackRender(res, response))
} else {
callbackRender(result, response)
}
}
}
The two failing tests look like :
it('should call the callback render method when httpResponse is a promise', (done) => {
const mock = sinon.mock(injector)
const ctrl = new UserControllerMock()
const routes = routeParser.parseRoutes()
mock.expects('get').returns(ctrl)
const spy = chai.spy.on(callbackRender)
callback(injector, routes[3])(request, response).then((res) => {
expect(spy).to.have.been.called.once
mock.verify()
mock.restore()
done()
})
})
it('should call the callback render method when httpResponse is not a promise', () => {
const mock = sinon.mock(injector)
const ctrl = new UserControllerMock()
const routes = routeParser.parseRoutes()
mock.expects('get').returns(ctrl)
const spy = chai.spy.on(callbackRender)
callback(injector, routes[1])(request, response)
expect(spy).to.have.been.called.once
mock.verify()
mock.restore()
})
It seems that chai-spies isn't able to detect that my callbackRender function is called in the callback method.
The fact is that, when I log my method, I pass inside of it each time I need it to do.
Does anybody has an idea ?
EDIT : The request / response definition in beforeEach
beforeEach(() => {
request = {
body: {},
params: {},
query: {}
}
response = {
send: () => {
},
render: () => {
}
}});
Spies/stubs/mocks can only work if they can replace the original function (with a wrapped version), or if they get passed explicitly (which isn't the case in your code).
In your case, callbackRender isn't replaced (it can't be, due to the const but also because it has no "parent" object in which it can be replaced), so any code that will call it (like callback) will call the original function, not the spy.
A solution depends on how exactly your code is structured.
If callback and callbackRender are located in a separate module together, you might be able to use rewire to "replace" callbackRender with a spy.
However, one caveat is that rewire also can't replace const variables, so your code would have to change.

Categories