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: [],
});
Related
Simplified problem case:
export class MyClass {
constructor() {
this.myMethod();
}
myMethod() {
console.log(42);
}
}
Testing the constructor:
describe('CLASS: MyClass', () => {
let sut: MyClass;
beforeEach(() => {
jest.clearAllMocks();
sut = new MyClass();
});
describe('CONSTRUCTOR', () => {
test('should construct correctly and call myMethod', () => {
const spy = jest.spyOn(sut, 'myMethod').mockImplementationOnce(jest.fn());
expect(sut).toBeTruthy();
expect(spy).toHaveBeenCalled();
});
});
});
Of course this doesn't work, as the spy is initiated after sut is constructed, so it can't register the call.
Neither is it possible to initiate the spy before sut, as it can't spy on something that doesn't exist yet.
Nor did I have success trying to spy on MyClass.prototype.
Sure, I could spy on the implementation details of myMethod (basically jest.spyOn(console, 'log'). But that defies the separation of units for testing.
It's probably trivial, but what am I missing, how to get this very simple test to work?
You should spy on MyClass instead of the instantiated class, sut.
You can still spy on methods without mocking the implementation but it's probably better here to avoid the code in it getting executed and logging 42 in the console.
describe('CLASS: MyClass', () => {
let sut: MyClass
let spy: jest.SpyInstance = jest.spyOn(MyClass.prototype, 'myMethod')
beforeEach(() => {
jest.clearAllMocks()
spy.mockImplementationOnce(jest.fn())
sut = new MyClass()
})
describe('CONSTRUCTOR', () => {
test('should construct correctly and call myMethod', () => {
expect(sut).toBeTruthy()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
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!
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.
I wrote mock for my class and everything works, when I test creating instance, but not, when I test static method. How to fix this?
Here is my testing class:
class Order {
async add(items) {
const order = new OrderModel({items});
await order.save();
//...
}
async find(items) {
const query = OrderModel.find({
//condition
});
//...
}
}
My OrderModel mock:
const mockOrderModel = {
save: jest.fn(),
find: jest.fn(),
exec: jest.fn()
};
jest.mock('../order/order.model', () => {
return jest.fn().mockImplementation(() => {
return {
save: mockOrderModel.save,
find: mockOrderModel.find,
exec: mockOrderModel.exec
};
});
});
And tests for both methods:
//WORKS
it('add()', async () => {
await order.add(['']);
expect(OrderModel).toHaveBeenCalledTimes(1);
expect(mockOrderModel.save).toHaveBeenCalledTimes(1);
});
//NOT WORKS
it('find()', async () => {
await order.find(['']);
expect(mockOrderModel.find).toHaveBeenCalledTImes(1);
});
OrderModel.find() method is not called
That's because find on mockOrderModel is defined as a property of an object and not a static method. So basically you're creating a mock object for Order with find property that must be called on an instance. Static method of Order is not mocked. To do so you can try:
Order.find = jest.fn()
I have an issue with a unit test of a function which calls a class. It seems that it always calls the "official" class instance and not my mocked class. I'm not able to force my function to use my mocked instance...
There is a file with the function I want to test:
const myClass = require('./myClass');
const instance = new myClass();
module.exports.functionToTest = async function () {
// Some stuff...
const value = await instance.myMethod();
// Some stuff that define a result variable (partially with value).
return result;
}
There is a file with my class definition:
module.exports = class myClass {
async myMethod() {
const result = await someStuffWillResolveMaybeTrueOrFalse();
console.log('We used the original myMethod... Mocking has failed.');
return result;
}
}
There is a spec file:
const myFile = require('./myFile');
const myClass = require('./myClass');
describe('My test', async () => {
it('should mock myClass.myMethod in order to return false', () => {
const instance = new myClass();
instance.myMethod = jest.fn().mockResolvedValue(false);
const result = await myFile.functionToTest();
expect(result).toBeTruthy();
}
}
Unfortunately my test is passing (because myMethod return "true") and log "We used the original myMethod... Mocking has failed."
So I want to make my test always fail by mocking that myMethod to return false.
Can you help me? Thanks for your time.
Hm. I've found a solution.
See. A change in my file with the target function.
const myClass = require('./myClass');
// const instance = new myClass(); <== Not here...
module.exports.functionToTest = async function () {
const instance = new myClass(); // <== ...but there.
// Some stuff...
const value = await instance.myMethod();
// Some stuff that define a result variable (partially with value).
return result;
}
And my spec file :
const myFile = require('./myFile');
// I specify to Jest that I'll mock a file
jest.mock('./myClass');
const myClass = require('./myClass');
// I prepare the mock function. In that case a promise wich resolve 'false'
const mMock = jest.fn().mockResolvedValue(false);
// I mock the method 'myMethod' in 'myClass'
myClass.mockImplementation(() => {
return {
myMethod: mMock
};
});
// Then, I just take the test
describe('My test', async () => {
it('should mock myClass.myMethod in order to return false', () => {
const result = await myFile.functionToTest();
expect(result).toBeFalsy();
}
}