jest test async class method - javascript

class person {
constructor() {
...
}
fetchPersonData() {
fetch(api).then((return response) => {
return response;
})
}
async initializePerson() {
const data = await fetchPersonData();
}
}
I'm trying to write a test for initializePerson function but it doesn't get called.
test("pass initializePerson", async( ) => {
const personInstance = new person();
let spy = jest.spyOn(personInstance, "initializePerson").mockImplementationOnce(async () => {
return mock;
});
await personInstance.initializePerson();
expect(spy).toBeCalledTimes(1);
});

There are few mistakes.
(return response) is wrong.
You're spying the method which you're calling. It doesn't make any sense. Spy methods which you've used inside the methods before calling the actual method.
I think you're looking for something like this.
class Person {
constructor() {...}
fetchPersonData() {
return fetch(api).then(response => response.json())
}
async initializePerson() {
const data = await fetchPersonData();
}
}
and test.js
test("pass initializePerson", async () => {
const personInstance = new Person();
const fetchPersonDataSpy = jest.spyOn(personInstance, "fetchPersonData").mockResolvedValue({...})
await personInstance.initializePerson();
expect(fetchPersonDataSpy).toBeCalledTimes(1);
});
If you want test the other method and mock the fetch, read this article. This will help you!

Related

Jest Unit Testing function that calls a second one that returns a promise

Edited Question with vazsonyidl suggestions applied
I have to write unit tests for a function similar to this one:
import {External} from 'ExternalModule';
async functionA(){
this.functionB().then((data) => {
External.functionC(options);
console.log("Reached1");
}).catch((data) => {
const { OnError = "" } = data || {}
if(OnError) {
External.functionC(anotherOptions);
console.log("Reached2");
}
})
}
functionB() {
return new Promise(() => {
});
}
As functionC belongs to another module, I placed a mock of it in the _mocks_folder:
//_mocks_/ExternalModule.ts
export var External: ExternalClass = {
functionC(){}
}
class ExternalClass{
constructor(){};
functionC(){};
}
I have mocked functionB in two diferent ways for testing the then and the catch :
it("should test then block", () => {
functionB = jest.fn(() => {return Promise.resolve()});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
it("should test catch block", () => {
const err = { OnError: "Error" };
functionB = jest.fn(() => {return Promise.reject(err)});
const functionSpy = jest.spyOn(ExternalModule.External, 'functionC');
void functionA().then(() => {
expect(functionSpy).not.toHaveBeenCalled();
});
})
What I am trying to do is expect that functionC was called and called with the correct params, but the test is always passing even if I test if functionC was not called.
What am I doing wrong?
Jest does not wait for the async code to complete before doing assertions.
You can use the following function:
const waitForPromises = () => new Promise(setImmediate);
to force Jest to wait for promises to complete before continuing like so:
it("does something", async () => {
promiseCall();
await waitForPromises();
expect(something).toBe(something)
});
I think when this function catch error, this error should have an 'OnError' property so the functionC can run.
const { OnError = "" } = data || {}
if(OnError) {
ExternalClass.functionC(anotherOptions);
}
change you response error data to return Promise.reject({OnError: '404'}) may solve this problem.
Because you are not providing it to your class.
The following code is working for me:
class A {
async functionA() {
this.functionB().then((data) => {
this.functionC(); // It woll log aaa here, you need this one.
}).catch((data) => {
const {OnError = ''} = data || {};
if (OnError) {
console.log('onerror');
}
});
}
functionB() {
return new Promise(() => {
});
}
functionC() {
return 2;
}
}
describe('a', () => {
it('test', () => {
const a = new A();
a.functionB = jest.fn(() => Promise.resolve());
const functionBSpy = jest.spyOn(a, 'functionC');
void a.functionA().then(() => {
expect(functionBSpy).toHaveBeenCalledTimes(1);
});
});
});
Hope this helps, any comment appreciated.
As you provided no information about your functionB I mocked something that may suitable for you.
Your original problem is that Jest does not wait for your callbacks to settle. It does the assertion although, even if your function calls happen later, Jest will not recognise them and says that no call ever occurred.
There are several docs available, for example Jest's one here

How to wait for constructor to finish?

I have a class constructor that has async elements. Later when I create an instance of this class, I want to read a property that will only exist when the constructor finished 100%. I always run into problem Can not read property 'id' of undefined. I'm almost sure this is a problem about async .. await.
class NewPiecePlease {
constructor(IPFS, OrbitDB) {
this.OrbitDB = OrbitDB;
(async () => {
this.node = await IPFS.create();
// Initalizing OrbitDB
this._init.bind(this);
this._init();
})();
}
// This will create OrbitDB instance, and orbitdb folder.
async _init() {
this.orbitdb = await this.OrbitDB.createInstance(this.node);
console.log("OrbitDB instance created!");
this.defaultOptions = { accessController: { write: [this.orbitdb.identity.publicKey] }}
const docStoreOptions = {
...this.defaultOptions,
indexBy: 'hash',
}
this.piecesDb = await this.orbitdb.docstore('pieces', docStoreOptions);
await this.piecesDb.load();
}
...
}
Later I create an instance of this class like this:
(async () => {
const NPP = new NewPiecePlease;
console.log(NPP.piecesDb.id);
// This will give 'undefined' error
})();
How can I tell NodeJS that I want new NewPiecePlease to fully finish? await console.log(NPP.piecesDb.id); does not help, which is understandable, because it won't understand what I'm await-ing. What is the correct way to do this?
You could use a factory for this. They are very good for doing complex, potentially async object creation and to keep your constructors clean and focused.
class NewPiecePlease {
constructor(orbitdb, node, pieceDB) {
this.orbitdb = orbitdb;
this.node = node;
this.pieceDB = pieceDB;
}
static async create(IPFS, OrbitDB) {
const node = await IPFS.create();
const orbitdb = await OrbitDB.createInstance(node);
console.log("OrbitDB instance created!");
const defaultOptions = {
accessController: {
write: [orbitdb.identity.publicKey]
}
}
const docStoreOptions = { ...defaultOptions, indexBy: 'hash' };
const piecesDb = await orbitdb.docstore('pieces', docStoreOptions);
await piecesDb.load();
return new NewPiecePlease(orbitdb, node, piecedb);
}
}
As you can see the create method does all the async stuff and just passes the results into the constructor where it really doesn't have to do anything except assign and maybe validate some arguments.
(async () => {
const NPP = await NewPiecePlease.create(IPFS, OrbitDB);
console.log(NPP.piecesDb.id);
// This will give 'undefined' error
})();

Successfully passed `this` context to an async function but how to retrieve `this` context when it is done NodeJS?

I am able to console log this in the context I want as long as I'm in the event listener. But I can't seem to update or change the more global object. How can I return the value to update a more global object?
I've tried to call the functions with bind, I also thought maybe it is an async problem and tried to solve using promises.
const { createReadStream } = require("fs");
const { createInterface } = require("readline");
export default class Process {
constructor() {
let self = this;
let file = [];
let lineReader = createInterface({
input: createReadStream(this.filePath)
});
lineReader
.on("line", line => {
file.push(line);
})
.on("close", () => {
self.file = file;
console.log(self);
return self;
});
}
console.log(this);
}
}
I expect the output to be:
Process {
filePath: 'path/to/file',
file:
[ 'line1',
'line2'
] }
The actual output is:
Process {
filePath: 'path/to/file' }
The answer is that the createInterface method returns a promise making this an async issue. This guide helped me implement a Promise to solve the issue. Also used this reference to help with syntax:
const { createReadStream } = require("fs");
const { createInterface } = require("readline");
function processFile(filePath) {
let data = '';
return new Promise((resolve, reject) => {
createInterface({
input: createReadStream(filePath)
})
.on("line", line => {
data += line + '\n';
})
.on("close", f => {
resolve(data)
})
})
}

Unit testing chained promises

How would I unit test a class method that calls an imported class's method that is a promise? I have the following structure:
import { SomeClass } from 'some-library';
class MyClass extends AnotherClass {
myMethod() {
const someClass = new SomeClass();
return someClass.somePromiseMethod('someParam')
.then(response => response.data)
.then(response => {
// Do stuff
});
}
}
I have the following test
describe('myMethod', () => {
it('does something', async () => {
const inst = new MyClass();
const stub = sinon.stub(SomeClass, 'somePromiseMethod')
.resolves(Promise.resolve({
data: [],
}));
await inst.myMethod();
expect(stub.callCount).to.equal(1);
});
});
Which is still pretty bare as I'm not sure how to approach this. Would it be better to break down the code in the thens?
UPDATE
Apparently SomeClass is a singleton and sinon was throwing an error saying somePromiseMethod is a non-existent own property. I changed the stub to call on its prototype instead and now the stub is being called.
class MyClass extends AnotherClass {
myMethod() {
const someClassInstance = SomeClass.getInstance();
return someClassInstance.somePromiseMethod('someParam')
.then(response => response.data)
.then(response => {
// Do stuff
});
}
}
describe('myMethod', () => {
it('does something', async () => {
const inst = new MyClass();
const stub = sinon.stub(SomeClass.prototype, 'somePromiseMethod')
.resolves(Promise.resolve({
data: [],
}));
await inst.myMethod();
expect(stub.callCount).to.equal(1);
});
});
Now, since then second then would just return data, I could just put //Do stuff in a separate function and test that.
You are stubbing out the wrong method somePromiseMethod exists on the prototype of SomeClass so you need to stub that instead. Sinon should let you do something like:
const stub = sinon.stub(SomeClass.prototype, 'somePromiseMethod')
// You may be able to remove the Promise.resolve as well, as I think resolves does this for you
.resolves({
data: [],
});

Stubing a class call from another function

I have 2 files controller.js and entity.js which interact with each other. I am testing controller.js, and it creates an instance of entity.js (class) and use one of its functions. How can I stub/mock/spy the call and the return of that method?
controller.js
const controller= async (req, res) => {
try {
...
const entity = new Entity({
...
});
const validation = await entity.validate();
...
return res.send()
}
} catch (error) {
return res.send(error)
}
};
Entity.js
class Entity{
constructor() {
...
}
...
async validate() {
...
return response;
}
}
Any idea how to test controller.js using supertest, sinon and chai?
Sinon will happily stub the function. Since it's a class method you just need to be sure to stub the function on the prototype:
const controller = async (req, res) => {
const entity = new Entity();
const validation = await entity.validate();
console.log(validation)
};
class Entity{
constructor() {}
async validate() {
return "real function";
}
}
// stub it
let stub = sinon.stub(Entity.prototype, 'validate')
stub.returns('stubbed function')
controller()
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/7.1.1/sinon.min.js"></script>
This solution uses Ava (but you should be able to adapt to Mocha easily). However I'm more familiar with testdouble. If you have no success with sinon (I'm sure you will), here's an alternative that you may want to consider.
So if we have burrito.js:
module.exports = class {
eat() {
return '🌯';
}
};
And lunch.js:
var Burrito = require('./burrito');
module.exports = () => (new Burrito()).eat();
Then in your test:
const td = require('testdouble');
const test = require('ava');
test('swap burrito', t => {
td.replace('./burrito', class FakeBurrito {
eat() {
return '🌮';
}
});
const lunch = require('./lunch');
t.is(lunch(), '🌮'); // PASS
t.is(lunch(), '🌯'); // FAIL
});
The key is to require your dependency (a burrito) before your subject under test (your lunch) requires it, so that you have time to fake it.

Categories