Chai spies and async calls not call once - javascript

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.

Related

Nock interceptors chaining, second mock ignored

A simple example of a mocking chain of requests with nock.
const request = require('request-promise');
module.exports = () => {
const URL1 = 'https://my.host.com/a/b/c/d';
const URL2 = 'https://my.host.com/a/b/x/y?k=v';
const options = {
method: 'POST',
uri: URL2,
body: {
some: 'payload'
},
json: true
};
return request(URL1)
.then(() => request(options))
.catch(e => console.error(e))
};
and test for it:
require('should');
const nock = require('nock');
const testFn = require('./');
describe('Check endpoint requests', () => {
beforeEach(() => {
nock.disableNetConnect();
});
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});
it('should hit correct endpoints', () => {
const scope = nock(`https://my.host.com/a/b`, {
encodedQueryParams: true,
})
.get('/c/d')
.reply(200)
.post('/x/y', {
some: 'payload'
})
.query({k: 'v'})
.reply(200);
testFn().then(() =>
scope.isDone().should.be.true()
);
});
});
As a result during the tests, the second "POST" request mock is completely ignored. After the hitting first mock URL1 - nock clearing the pending mocks for that scope and marks it as done.
The thing which counts I thing is that the basic URL is the same.
Is it a bug, or I use it incorrectly.
You have a few minor issues in your test.
First, the value passed to nock should just be the origin and shouldn't include part of the path. Instead, in your case, get and post should have the full path.
Second, you want to remove encodedQueryParams: true. That flag means the interceptor is created using already encoded query/search params, however, you're calling it like .query({k: 'v'}), which is not pre-encoded.
The last issue is that you weren't telling Mocha when the test was finished. So it was completing the test before having all of its results. There are two ways to achieve this. Either accept an argument in the it callback, done is the nomenclature. Or make the callback async and await your requests. I've implemented the latter below.
it('should hit correct endpoints', async () => {
const scope = nock('https://my.host.com')
.get('/a/b/c/d')
.reply(200)
.post('/a/b/x/y', {
some: 'payload'
})
.query({k: 'v'})
.reply(200);
await testFn();
scope.isDone().should.be.true();
});

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

Jest not recognizing spy is called

I'm having a little trouble confirming my function is called using Jest. I've got two mocked functions. One is just mocked to return a promise, the other is a simple spy that should be called in the then() block of the first.
Below, in the test, you will see two expectations. The first expectation passes. The second does not. i.e. expect(sendSpy).toHaveBeenCalled() passes but expect(sendCallbackSpy).toHaveBeenCalled() does not.
However, when I put the console.log statements in the file (as below), both execute (i.e. the 'before' and 'after'), and if I console log the window.props.onSend it confirms that the mocked function is present. So it looks like the function should be called.
Another thing of note is that my implementation requires me to pass in the callback in a props object from my window. Further, to mock things on the window in Jest, you just mock global as I'm doing below. I don't think that is relevant to the issue but worth pointing out nonetheless.
Could it be that the expectation is run before the actual function is called in the then() block?
export class MyButton extends Component {
handleClick = () => {
this.props.send(this.props.url).then(res => {
console.log('before', window.props.onSend)
window.props.onSend(res.data)
console.log('after')
})
}
render() {
return <button onClick={handleClick} />
}
}
//test
test('it calls the identity function when button is clicked', () => {
const sendSpy = jest.fn(() => { return Promise.resolve({ data: 'hello' }) })
const sendCallbackSpy = jest.fn()
global.props = { onSend: sendCallbackSpy }
wrapper = shallow(<MyButton send={sendSpy} } />)
const button = wrapper.find('button')
button.simulate('click')
expect(sendSpy).toHaveBeenCalled()
expect(sendCallbackSpy).toHaveBeenCalled()
})
You need to wait for the promise before testing the second spy:
test('it calls the identity function when button is clicked', async() => {
const request = Promise.resolve({ data: 'hello' })
const sendSpy = jest.fn(() => request)
const sendCallbackSpy = jest.fn()
global.props = { onSend: sendCallbackSpy }
wrapper = shallow(<MyButton send={sendSpy} } />)
const button = wrapper.find('button')
button.simulate('click')
expect(sendSpy).toHaveBeenCalled()
await request
expect(sendCallbackSpy).toHaveBeenCalled()
})

Multiple unit tests on the same data accessible in a callback

I implemented a parser to extract data from an Excel file and return an object with a certain structure. To make the code work asynchronously, I used a simple callback scheme for the further processing of the extracted data:
parse(inputFile, callback) {
const workbook = new Excel.Workbook();
workbook.xlsx.readFile(inputFile).then((workbook) => {
// Parsing
callback(result);
});
}
Now I want to write a unit test for this routine using Mocha and Chai. Of course I can simply put the expect()statements in the callback function:
const ExcelParser = require('ExcelParser');
var chai = require("chai");
const expect = chai.expect;
describe('Unit::Test parsing', () => {
const excelParser = new ExcelParser();
it('should parse the data correctly', (done) => {
excelParser.parse('sheet.xlsx', (data) => {
expect(data).to.have.property('mainContent');
expect(data['mainContent']).to.be.an('array');
});
done();
});
});
This works fine, but I want to write more tests for the returned data object in separate it() blocks. By doing it this way, the parsing would be repeated for every single subtest.
I tried putting the call of parse() in a before() block and define a callback method that stores the result object in a global variable, but obviously the parsing is not finished by the time the tests are run.
What is the best way to do this, if it is possible at all? Would it help if the parse() method returned a Promise rather than working with callbacks?
You should be able to achieve this by parsing in the before block passing done. Then test in the it statements as normal,
const ExcelParser = require('ExcelParser');
var chai = require("chai");
const expect = chai.expect;
describe('Unit::Test parsing', () => {
const excelParser = new ExcelParser();
let data = null;
before((done) => {
excelParser.parse('sheet.xlsx', (result) => {
data = result;
done();
});
})
it('should parse the data correctly', () => {
expect(data).to.have.property('mainContent');
expect(data['mainContent']).to.be.an('array');
});
it('should have prop foo', () => {
expect(data).to.have.property('foo');
});
...
});
Assuming excelParser.parse returned a promise, you wouldn't need passing done to before:
before(() => {
return excelParser.parse('sheet.xlsx')
.then((result) => {
data = result;
});
})

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.

Categories