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.
Related
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'm trying to unit test the following controller:
export class MyController extends BaseController {
constructor() {
super();
this.repository = new MyRepository();
}
public getData(req: Request, res: Response, next: NextFunction) {
this.repository.getData(req.params.param1).then((result) => {
return this.ok(req, res, result.resources) // calls ok() method from base controller
}, (err: Error) => {
next(err)
});
}
}
I would like to stub the MyRepository.getData which returns a Promise<MyResult>
I also want to stub the BaseController.ok method to ensure it's called with the data returned from repo.
Here is my test:
it("should call the repository", (done) => {
var mockReq = httpMocks.createRequest();
var mockResp = httpMocks.createResponse();
const mockNext: NextFunction = stub();
mockReq.params.param1 = "value1";
let sampleResult = new MyResult();
const getDataStub = stub(MyRepository.prototype, "getData").resolves(sampleResult);
const okStub = stub(MyController.prototype, "ok");
new MyController().getData(mockReq, mockResp, mockNext);
expect(getDataStub).to.have.been.calledWith("value1"); // passes ok
expect(okStub).to.have.been.called; // fails
done()
});
The test fails when checking, if the okStub has been called at least once. If I debug the code I can see that the BaseController.ok is actually called, but after the evaluation in test.
Looks like code inside of getData.then gone to another cycle at event loop.
Try to place done() inside of this.ok like this
stub(MyController.prototype, "ok").callsFake(() => {
done();
});
I have created a small project to show the issue.
There 5 files in this project.
A container file which contain all the dependencies injection; A service file which contain the function I need to run; A controller file which calls the function in service file; An app file which is the highest level application which will call all the controller files, in this example there is only 1; An index.js file which is the start point of the application.
container.js:
const {
createContainer,
asValue,
asFunction,
asClass,
} = require('awilix');
const container = createContainer();
const Controller = require('./controller');
const Service = require('./service');
container.register({
controller: asClass(Controller).singleton(),
service: asClass(Service).singleton(),
})
const App = require('./app');
container.register({
app: asClass(App).singleton(),
})
module.exports = container;
service.js:
module.exports = class Service {
doService() {
return new Promise(resolve => {
setTimeout(() => resolve("Hello from service!"), 2000);
});
}
}
controller.js:
module.exports = class Controller {
constructor({ service }) {
this.service = service;
}
doWork() {
this.service.doService().then(response => {
this.message = response;
return this.message;
})
}
}
app.js:
module.exports = class App {
constructor({ controller }) {
this.controller = controller;
}
async start() {
try {
this.doc = await this.controller.doWork();
} catch (err) {
}
console.log(this.doc);
}
}
index.js:
const container = require('./container');
const app = container.resolve('app');
app.start();
My goal is that I can see the doc attribute in app.js becomes 'Hello from service!' and be able to console.log it out. The call sequence should be:
index.js --> app.js --> controller.js --> service.js --> controller.js --> app.js
All the dependencies are injected in the container.js using awilix js.
I am very sure is something that I messed up with return it from promise in controller.js, because if I use other sync function it worked.
Please let me know why I am wrong, how to make it work.
You don't return the promise that you create in controller.doWork(). In your "start" function when you await the result of controller.doWork(), there is no value. The function does not return a promise, or anything for that matter, to "await."
module.exports = class Controller {
constructor({ service }) {
this.service = service;
}
//Needs to return a value.
doWork() {
// This is a promise, you need to return it.
//this.service.doService().then(response => {
// this.message = response;
// return this.message;
//})
return this.service.doService().then(response => {
this.message = response;
return this.message;
})
}
The await statement in app.js is syntax sugar for something like the following:
this.controller.doWork().then(response => {
this.doc = response;
});
Instead of writing that, we can use await to "automatically" unwrap the value returned by the promise, and assign it to a variable.
It also pauses the function "start", which allows us to avoid having to write the remainder of the function within the "then" statement:
this.doc = await this.controller.doWork();
if (this.doc.type === "error") {
throw new Error(this.doc)
}
Is much cleaner than:
this.controller.doWork().then(response => {
this.doc = response;
if (this.doc.type === "error") {
throw new Error(this.doc)
}
});
Especially as the code that is dependent upon the value of doWork() gets longer and/or more complicated.
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();
}
}
I'm trying to use sinon stub to replace a function that might take along time. But when I run the tests, the test code doesn't seem to be using the sinon stubs.
Here is the code I'm trying to test.
function takeTooLong() {
return returnSomething();
}
function returnSomething() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('ok')
}, 1500)
})
}
module.exports = {
takeTooLong,
returnSomething
}
and this is the test code.
const chai = require('chai')
chai.use(require('chai-string'))
chai.use(require('chai-as-promised'))
const expect = chai.expect
chai.should()
const db = require('./database')
const sinon = require('sinon')
require('sinon-as-promised')
describe('Mock the DB connection', function () {
it('should use stubs for db connection for takeTooLong', function (done) {
const stubbed = sinon.stub(db, 'returnSomething').returns(new Promise((res) => res('kk')));
const result = db.takeTooLong()
result.then((res) => {
expect(res).to.equal('kk')
sinon.assert.calledOnce(stubbed);
stubbed.restore()
done()
}).catch((err) => done(err))
})
I get an assertion error
AssertionError: expected 'ok' to equal 'kk'
+ expected - actual
-ok
+kk
What am I doing wrong? Why isn't the stub being used ? The test framework in Mocha.
Sinon stubs the property of the object, not the function itself.
In your case you are exporting that function within an object.
module.exports = {
takeTooLong,
returnSomething
}
So in order to properly call the function from the object, you need to replace your function call with the reference to the export object like :
function takeTooLong() {
return module.exports.returnSomething();
}
Of course based on your code, you can always refactor it :
var exports = module.exports = {
takeTooLong: function() { return exports.returnSomething() }
returnSomething: function() { /* .. */ }
}
You might want to have a look at Proxyquire to stub/spy directly exported functions.
https://www.npmjs.com/package/proxyquire/