Test callback function with jest - javascript

I'm trying to test a function with a callback inside. I set up a mock function, but I also need to test a callback.
I've tried to separate it as another mock function, but it doesn't counted as covered.
Function I'm trying to test:
export const checkDescription = async page => {
const metaDescription = await page.$eval(
'meta[name="description"]',
description => description.getAttribute("content")
);
return metaDescription;
};
I've mocked the page function :
const page = {
$eval: jest.fn(() => "Value")
};
my test :
test("Should return description", async () => {
expect(await checkDescription(page)).toBe("Value");
expect(page.$eval).toHaveBeenCalled();
});
I've tried to separate description :
const description = {
getAttribute: jest.fn(() => "Value")
};
but I don't think that it's a correct way to cover description inside $eval.

You're close!
The description arrow function is passed to your page.$eval mock function so you can use mockFn.mock.calls to retrieve it.
Once you've retrieved it, you can call it directly to test it and get full code coverage:
test("Should return description", async () => {
expect(await checkDescription(page)).toBe("Value"); // Success!
expect(page.$eval).toHaveBeenCalled(); // Success!
const description = page.$eval.mock.calls[0][1]; // <= get the description arrow function
const getAttributeMock = jest.fn(() => 'mock content');
expect(description({ getAttribute: getAttributeMock })).toBe('mock content'); // Success!
expect(getAttributeMock).toHaveBeenCalledWith('content'); // Success!
// Success! checkDescription now has full code coverage
});

I receive async messages from serial port via callbacks. Try to read here:
https://jest-bot.github.io/jest/docs/asynchronous.html
import { InpassTerminal } from "../src/main.js"
jest.setTimeout(45000);
describe('Basic tests', () => {
test('1. Host connection', async (done) => {
await new Promise( resolve => setTimeout(resolve, 500) );
const commandTest = {actionCode: '12345', terminalId: '1019****'}
function cb (data) {
if (data.operationCode == 12345) {
const actualStatus = Buffer.from(data.status, "ascii")
const expectedStatus = '1'
expect(actualStatus.toString()).toBe(expectedStatus)
done()
}
}
const terminal = new InpasTerminal()
terminal.exec('/dev/ttyPos0', commandTest, cb)
})

Related

How to automate testing function with click handler that have async await inside the function using karma-jasmine?

So i'm trying to testing on my button that run the function asynchronously. this is my button logic looks like.
// Function below will run when user click the button
this._pageModule.favoriteButtonCallback = async () => {
try {
// I want to expect run after this await below is done
await this._favoriteRestaurants.PutRestaurant(this._restaurant);
console.log(
'console log in button',
await this._favoriteRestaurants.GetAllRestaurant(),
);
this._renderButton();
return Promise.resolve(
`Success add ${this._restaurant.name} to favorite!`,
);
} catch (err) {
this._renderButton();
return Promise.reject(
new Error(
`Failed add ${this._restaurant.name} to favorite! Error: ${err}`,
).message,
);
}
};
and this is my test
fit('should be able to add the restaurant to favorite', async () => {
expect((await RestaurantIdb.GetAllRestaurant()).length).toEqual(0);
// spyOn(RestaurantIdb, 'PutRestaurant');
document.body.innerHTML = `<detail-module></detail-module>
<modal-element></modal-element>`;
const pageModule = document.querySelector('detail-module');
await FavoriteButtonInitiator.init({
pageModule,
restaurant,
favoriteRestaurants: RestaurantIdb,
});
pageModule.restaurantDetail = restaurant;
await pageModule.updateComplete;
const favoriteButton = pageModule.shadowRoot
.querySelector('[aria-label="favorite this restaurant"]')
.shadowRoot.querySelector('button');
// 1. Simulate user click the button
favoriteButton.dispatchEvent(new Event('click'));
// expect(RestaurantIdb.PutRestaurant).toHaveBeenCalled();
const restaurants = await RestaurantIdb.GetAllRestaurant();
console.log('console log from test', restaurants);
expect(restaurants).toEqual([restaurant]);
});
i'm using lit-element, simply it similar with react, i have custom element <define-module> with button inside. then i give the required properties to it, then it will render.
This is my test log Test log
as you can see the console log from the test ran before the console log that i put in the button. and it is empty array.
what i want is when click event dispatched. the next line in the test wait until the asynchronous function in the button done, how do i make it possible?
What have i done:
i have tried to console log them.
i have tried to using done in jasmine, but it doesn't work since i using async/await in the test.
I have tried use spyOn, but i don't really understand how to spy indexedDb
UPDATE
So i have found what caused problem, here i have simplified my code.
/* eslint-disable */
import { openDB } from 'idb';
import { CONFIG } from '../src/scripts/globals';
const { DATABASE_NAME, DATABASE_VERSION, OBJECT_STORE_NAME } = CONFIG;
const dbPromise = openDB(DATABASE_NAME, DATABASE_VERSION, {
upgrade(database) {
database.createObjectStore(OBJECT_STORE_NAME, { keyPath: 'id' });
},
});
const RestaurantIdb = {
async GetRestaurant(id) {
return (await dbPromise).get(OBJECT_STORE_NAME, id);
},
async GetAllRestaurant() {
return (await dbPromise).getAll(OBJECT_STORE_NAME);
},
async PutRestaurant(restaurant) {
if (await this.GetRestaurant(restaurant.id)) {
return Promise.reject(
new Error('This restauant is already in your favorite!').message,
);
}
return (await dbPromise).put(OBJECT_STORE_NAME, restaurant);
},
async DeleteRestaurant(id) {
if (await this.GetRestaurant(id)) {
return (await dbPromise).delete(OBJECT_STORE_NAME, id);
}
return Promise.reject(
new Error('This restauant is not in favorite!').message,
);
},
};
describe('Testing RestaurantIdb', () => {
const removeAllRestaurant = async () => {
const restaurants = await RestaurantIdb.GetAllRestaurant();
for (const { id } of restaurants) {
await RestaurantIdb.DeleteRestaurant(id);
}
};
beforeEach(async () => {
await removeAllRestaurant();
});
afterEach(async () => {
await removeAllRestaurant();
});
it('should add restaurant', async () => {
document.body.innerHTML = `<button></button>`;
const button = document.querySelector('button');
button.addEventListener('click', async () => {
await RestaurantIdb.PutRestaurant({ id: 1 });
});
button.dispatchEvent(new Event('click'));
setTimeout(async () => {
const restaurants = await RestaurantIdb.GetAllRestaurant();
console.log('console log in test', restaurants);
expect(restaurants).toEqual([{ id: 1 }]);
}, 0);
});
});
And this is the result Test Result
I assume that IndexedDb takes times to put my restaurant data. and i still can't figure out how to fix it.
If you were using Angular, you would have access to fixture.whenStable(), and fakeAsync and tick() which wait until promises are resolved before carrying forward with the test.
In this scenario, I would try wrapping what you have in the test in a setTimeout
fit('should be able to add the restaurant to favorite', async () => {
expect((await RestaurantIdb.GetAllRestaurant()).length).toEqual(0);
// spyOn(RestaurantIdb, 'PutRestaurant');
document.body.innerHTML = `<detail-module></detail-module>
<modal-element></modal-element>`;
const pageModule = document.querySelector('detail-module');
await FavoriteButtonInitiator.init({
pageModule,
restaurant,
favoriteRestaurants: RestaurantIdb,
});
pageModule.restaurantDetail = restaurant;
await pageModule.updateComplete;
const favoriteButton = pageModule.shadowRoot
.querySelector('[aria-label="favorite this restaurant"]')
.shadowRoot.querySelector('button');
// 1. Simulate user click the button
favoriteButton.dispatchEvent(new Event('click'));
// expect(RestaurantIdb.PutRestaurant).toHaveBeenCalled();
setTimeout(() => {
const restaurants = await RestaurantIdb.GetAllRestaurant();
console.log('console log from test', restaurants);
expect(restaurants).toEqual([restaurant]);
}, 0);
});
The things in the setTimeout should hopefully happen after the asynchronous task of the button click since promises are microtasks and setTimeout is a macrotask and microtasks have higher priority than macrotasks.

Node: how to test multiple events generated by an async process

I need to test an async Node.js library which periodically generates events (through EventEmitter) until done. Specifically I need to test data object passed to these events.
The following is an example using mocha + chai:
require('mocha');
const { expect } = require('chai');
const { AsyncLib } = require('async-lib');
describe('Test suite', () => {
const onDataHandler = (data) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
};
it('test 1', async () => {
const asyncLib = new AsyncLib();
asyncLib.on('event', onDataHandler); // This handler should be called/tested multiple times
await asyncLib.start(); // Will generate several 'events' until done
await asyncLib.close();
});
});
The problem is that even in case of an AssertionError, mocha marks the test as passed and the program terminates with exit code 0 (instead of 1 as I expected).
The following uses done callback instead of async syntax, but the result is the same:
require('mocha');
const { expect } = require('chai');
const { AsyncLib } = require('async-lib');
describe('Test suite', () => {
const onDataHandler = (data) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
};
it('test 1', (done) => {
const asyncLib = new AsyncLib();
asyncLib.on('event', onDataHandler);
asyncLib.start()
.then(asyncLib.close)
.then(() => done());
});
});
I have also tried with a "pure" Node.js approach using the native assert.ok without any 3rd part library:
const { strict: assert } = require('assert');
const { AsyncLib } = require('async-lib');
const test = async () => {
const onDataHandler = (data) => {
assert.ok(data.foo != null);
assert.ok(data.bar != null);
assert.ok(data.bar.length > 0);
};
asyncLib.on('event', onDataHandler);
const asyncLib = new AsyncLib();
await asyncLib.start();
await asyncLib.close();
}
(async () => {
await test();
})();
Even in this case, an AssertionError would make the program to terminate with exit code 0 instead of 1.
How can I properly test this code and make the tests correctly fail in case of an assertion error?
There are some things that you need to fix to make it works:
Make your test async, because the test is going to execute the expects after a certain event is received meaning it's going to be asyncronous.
Your event handler in this case onDataHandler should receive the done callback because there is the way how you can indicate to mocha that the test was finished successful as long as the expects don't fail.
I wrote some code and tested it out and it works, you have to make some changes to adapt your async library though:
describe('Test suite', function () {
const onDataHandler = (data, done) => {
expect(data.foo).to.exist;
expect(data.bar).to.exist;
expect(data.bar.length).to.be.greaterThan(0);
done();
};
it('test 1', async function (done) {
eventEmitter.on('event', (data) => onDataHandler(data, done));
setTimeout(() =>{
eventEmitter.emit('event', {
})
}, 400)
});
});

Async await test cases failed using jest javascript testing library

I am using Jest testing Library for some simple async/await functions. But it's failing again and again as I am very new to jest. and can you please answer what expect.assertions(1) do here
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: 1,
name: "test",
age: 20,
});
}, 1000);
});
}
test("test async await", async () => {
const data = await fetchData();
expect(data.id).toBe(1);
});
test("async await error", async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch("error");
}
});
As I pointed out in the comments, the test for the "failure" case doesn't really make sense if this fetchData is the real function because it never 'rejects'. To test the failure case in Jest, you'd need to somehow trigger the Promise.reject case.
If we assume this fetchData is a wrapper on an api call or something else, we could imagine something like this.
You might have a library or module that is making api calls like:
// api.js
const api = {
actualFetchData: () => {
// this is the function that actually connects
// to a data source and returns data
},
};
module.exports = api;
And your fetchData function which you're trying to test looks like:
// fetchData.js
const api = require("./api");
function fetchData() {
return api.actualFetchData();
}
module.exports = fetchData;
Then, assuming this structure matches what you're working on, you can mock the internals of fetchData and test both success and failure cases by mocking actualFetchData and using mockResolvedValue and mockRejectedValue.
// fetchData.test.js
const fetchData = require("./fetchData");
const api = require("./api");
jest.mock("./api");
const mockApiFetch = jest.fn();
api.actualFetchData = mockApiFetch;
describe("when the underlying fetch resolves", () => {
beforeEach(() => {
mockApiFetch.mockResolvedValue({
id: 1,
name: "test",
age: 20,
});
});
test("test async await", async () => {
const data = await fetchData();
expect(data.id).toBe(1);
});
});
describe("when the underlying fetch fails", () => {
beforeEach(() => {
mockApiFetch.mockRejectedValue(new Error("failed to get data"));
});
test("async await error", async () => {
expect(() => fetchData()).rejects.toThrow("failed to get data");
});
});
You'll notice I didn't use the expect.assertions because it didn't seem like it added anything to the test. Instead, just used toThrow with text that matches the error.
I realize this is making some assumptions about a system that you haven't fully described in the initial question so this may not be exactly what you're trying to get at. Hopefully it's close.

Unit Test: Stub/rewire a function inside a server request

I want to test a route that makes external api calls.
I would like to stub the functionThatShouldBeStubbed so I can skip the external api call and focus on testing the route instead.
I am using Sinon and rewire, because if I understood correctly I cannot stub a function that was exported the way it currently is.
However, it seems like even though rewire replaced the function, my test is still making external api call. It seems like sinon is not aware that the function was rewired. How can I make this situation work?
//--------------------------
//../target.js
const functionThatShouldBeStubbed = async () => {
const results = await external_API_call();
return results;
}
module.exports = {
functionThatShouldBeStubbed,
/*more other functions*/
}
//--------------------------
//../index.js
app.use(require('endpoint.js'));
//--------------------------
//endpoint.js
const { functionThatShouldBeStubbed } = require("target.js");
router.post('endpoint', async(req, res) => {
//do lots of stuff
const results = await functionThatShouldBeStubbed();
if(results.error) { return res.status(207).send({ /*stuff */})}
//...more stuff
})
//--------------------------
//test.js
const server = require("../index.js");
const rewire = require('rewire')
const restoreTarget = rewire('../target.js');
describe("Should return appropriate error code to requester", function () {
it("Should return 207 in this case", function (done) {
const targetStub = sinon.stub().resolves({msg: 'fake results', statusCode: 207})
const targetRewired = restoreTarget.__set__("functionThatShouldBeStubbed", targetStub);
chai.request(server)
.post("/endpoint")
.send('stuff over')
.catch((error) => {
console.log("Error: ", error)
done();
})
.then((res) => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
restoreTarget();
targetStub.restore();
done();
})
})
})
Many thanks!
Edit: updated code for more detail
Edit2: updated code again to show import method
You shouldn't need rewire at all here based on how your module is being exported. The following should work
//test.js
const target = require ("../target");
const server = require("../index");
describe("Should return appropriate error code to requester", () => {
it("Should return 207 in this case", done => {
const targetStub = sinon
.stub(target, "functionThatShouldBeStubbed")
.resolves({msg: 'fake results', statusCode: 207})
chai.request(server)
.post("/endpoint")
.send('stuff over')
.then(res => {
expect(targetStub.callCount).to.equal(1);
res.should.have.status(207);
targetStub.restore();
done();
})
})
})

Jest check when async function gets called

I'm trying to test whether an async function (fire and forget) gets called.
Content.js
export async function fireAndForgetFunction() {
...
}
export async function getData() {
...
fireAndForgetFunction()
return true;
}
I would like to test if fireAndForgetFunction has been called more than once.
Current test
import * as ContentFetch from '../Content';
const { getData } = ContentFetch;
const { fireAndForgetFunction } = ContentFetch;
it('test',async () => {
const spy = jest.spyOn(ContentFetch, 'fireAndForgetFunction');
await getData();
expect(spy).toHaveBeenCalled();
})
The test result to an error saying
Expected number of calls: >= 1
Received number of calls: 0
How could I do this test?
If you don't want to await for fireAndForgetFunction in getData(), which I assume is the case, then providing a mock implementation of fireAndForgetFunction when creating the spy is your best option:
it('test', (done) => {
const spy = jest.spyOn(ContentFetch, 'fireAndForgetFunction')
.mockImplementation(() => {
expect(spy).toHaveBeenCalled();
done();
})
getData();
})

Categories

Resources