I want to use sinon to write test code for the business logic of a service using ExpressJS and Mongoose.
I wrote the following test code, but findOneService takes only id as an argument and returns a document with that id.
//service_test.js
const sinon = require('sinon');
// Service
const { findOneService } = require('../services/service');
// Schema
const Post = require('../models/mongoose/schemas/post');
describe('findOneService', () => {
let find;
beforeEach(() => {
find = sinon.stub(Post, 'findOne');
});
afterEach(() => {
find.restore();
});
it('should findOne', async () => {
const id = ???;
...?
});
})
//service.js
exports.findOneDocument = async (id) => {
const result = await Post.findOne({_id: id});
if (!result) {
throw new Error('404');
}
return result;
};
How can I define the result of this to pass the test code?
To test this kind of behaviour, I strongly suggest an integration test (with an embedded/dockerized MongoDB, for example). This would allow to test-drive more things than just the service, such as schema, migration, db config.
However, if you just want to test-drive the if (!result)... logic, you can stick with sinon. What you're missing is stubbing the return value:
it('returns the document if found', async () => {
find.returns('a post');
expect(await findOneService.findOneDocument('id')).toReturn('a post');
});
it('throw error when document does not exist', async () => {
find.returns(null);
expect(() => await findOneService.findOneDocument('non-existent id')).toThrow(Error);
});
Related
I have a very basic Express.js app which I use Jest and Supertest to test. The routes are not set up until the database is connected:
class App {
public app: express.Application;
public mainRoutes: Util = new Util();
constructor() {
this.app = express();
AppDataSource.initialize()
.then(() => {
// add routes which rely on the database
this.mainRoutes.routes(this.app);
})
.catch((error) => console.log(error));
}
}
export default new App().app;
Here is my test:
describe("Util", function () {
test("should return pong object", async () => {
const res = await request(app).get("/ping");
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual({ message: "pong" });
});
});
Since I put in the promise, this has been 404ing. I can't add async to the constructor. I tried refactoring the class to separate the connection with setting up the routes, but it didn't seem to help.
This works:
test("should return pong object", async () => {
setTimeout(async () => {
const res = await request(app).get("/ping");
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual({ message: "pong" });
}, 1000);
});
But obviously I don't want to add a setTimeout. How is this usually done? I am new to testing.
Just remove the setTimeout() and await the call to the application. You should be initializing the application in the beforeAll() method, which I assume you have, to get the application up and running in the testing space. You should also mock your database connection, so you can fake the data you want back, and not have to wait for the external database to actually be available.
// Create a mock for your database, and have it return whatever you need
import <your-database-class> = require('database');
jest.mock('database', () => {
...
});
describe("Util", function () {
beforeAll(async () => {
app = await <whatever you do to launch your application>
});
test('should be defined', () => {
expect(app).toBeDefined();
});
test("should return pong object", async () => {
const res = await request(app).get("/ping");
expect(res.statusCode).toEqual(200);
expect(res.body).toEqual({ message: "pong" });
});
});
I have an async method triggered by a click event where I make a call to an API and then process the response, like this:
async confirmName () {
const {name, description} = this.form;
const [data, error] = await Pipelines.createPipeline({name, description});
if (error) {
console.error(error);
this.serviceError = true;
return false;
}
this.idPipelineCreated = data.pipeline_id;
return true;
}
The test looks like this:
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
pipelinesApi.createPipeline().then(data => {
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
}).catch(() => {})
})
A basic class mock:
export default class Pipelines {
constructor () {}
createPipeline () {}
}
I'm testing a success API call and I mock the API call returning a resolved promised. The problem is the coverage only covers the first two lines of the method, not the part where I assign the response of the API call. Is this the correct approach?
Edit:
Screenshot of coverage report:
Don't mix up await and then/catch. Prefer using await unless you have very special cases (see this answer):
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
const data = await pipelinesApi.createPipeline();
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
expect(wrapper.vm.serviceError).toBe(false);
})
No matter how I write, asynchronous problems occur.
test.js:
const auth = require('../methods/auth.js');
describe('test', () => {
test('test', async () => {
expect.assertions(1);
const data = await auth.signin();
return expect(data.success).toBeTruthy();
});
auth.js:
module.exports = {
async signin(data) {
try {
const res = await axios.post('/signin', data);
return res.data;
} catch (error) {
return error.response;
}
},
}
Each execution result is different.
You can test Promises as follows:
test('test awaiting it to resolve', () => {
// Don't await. Note the return; test callback doesn't need to be async
// What I do in my tests as its more readable and the intention is clear
return expect(auth.signin()).resolves.toEqual({success: true});
});
test('test the promises way', () => {
// Not better than above; traditional way; note 'return'
return auth.signin().then(data => { expect(data.success).toBeTruthy() });
});
Detailed notes from jest: https://jestjs.io/docs/asynchronous#promises
I am creating an API with NestJs and mysql.
My controller function for create a new entity is working well, however, I can't test the usecase where the response is a 400 error.
This is the controller function :
#Controller('pubs')
export class PubsController {
constructor(private readonly pubsService: PubsService) {}
#Post()
async create(#Body() createPubDto: CreatePubDto, #Res() res: Response): Promise<void> {
this.pubsService.create(createPubDto)
.then(() => res.status(201).json())
.catch(err => res.status(401).json({ err }));
}
}
And this is the test file :
describe('PubsController', () => {
let controller: PubsController;
let service: PubsService;
const mockResponse = () => {
const res: any = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PubsController],
providers: [PubsService, {
provide: getRepositoryToken(Pub),
useValue: {},
}],
}).compile();
controller = module.get<PubsController>(PubsController);
service = module.get<PubsService>(PubsService);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
describe('create success', () => {
const res = mockResponse();
it('Should create a pub', async () => {
const req = mockedPub;
jest.spyOn(service, 'create').mockResolvedValue(mockedPub);
await controller.create(req, res);
expect(res.status).toHaveBeenCalledWith(201);
});
it('Should return 400 if the body is not correct', async () => {
const req: any = {};
jest.spyOn(service, 'create').mockResolvedValue(req);
await controller.create(req, res);
expect(res.status).toHaveBeenCalledWith(400);
});
})
});
"Should create a pub" is working well, but when I give to the create function an empty object, the test give me a 201 res.status.
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: 400
Received: 201
Does anybody know why?
First, do you use any validation pipe anywhere in your code in order to validate the incoming CreatePubDto, e.g. in your main.ts file or in the pubService ?
Second, in your test that should fail, you have written jest.spyOn(service, 'create').mockResolvedValue(req); which resolves, and thus you won't catch any error at the controller level, which means you go in the .then(() => res.status(201).json()) of your controller logic.
You should refactor the test to:
it('Should return 400 if the body is not correct', async () => {
const error: any = { message: 'bad DTO provided', code: 400 }; // <== this is where you mock the logic of your service to throw an error
jest.spyOn(service, 'create').mockRejectedValue(error);
await controller.create(req, res);
expect(res.status).toHaveBeenCalledWith(400);
});
This way you're telling Jest to throw an error when the create method is called. I put example error that could be thrown, but feel free to use your own error format that will be thrown.
Also don't forget to align your error code returned in your controller with the one expected in your test. 400 would be the more appropriate I guess in this use case.
I am using a custom plugin to share a few persistence methods between some hapi modules:
const Mongoose = require('mongoose');
const Promise = require('bluebird');
const Models = require('./persistence/mongodb/models');
exports.register = (server, options, next) => {
Mongoose.Promise = Promise;
Mongoose.set('debug', 'true');
const mongoConnectionUri = process.env.MONGO_URI ||
'mongodb://localhost/foo';
Mongoose.connect(mongoConnectionUri, { useMongoClient: true })
.then(() => {
// eslint-disable-next-line no-param-reassign
server.app.db = Models;
});
next();
};
It is accessed as such, and works fine:
module.exports.doSomething = async (request, reply) => {
const Models = request.server.app.db;
const data = await Models.persistenceMethod();
reply(data);
}
However, I am struggling to mock persistenceMethod(). I tried with Sinon or monkey-patching:
test('retrieves data', async () => {
server.app.db = {
persistenceMethod: () =>
// eslint-disable-next-line no-unused-vars
new Promise((resolve, reject) => resolve({ foo: 'bar' }))
};
const expectedPayload = [ { foo: 'bar' } ];
const response = await server.inject({'GET', '/api/getFoo'});
const payload = response.payload;
expect(response.statusCode).to.equal(200);
expect(JSON.parse(payload)).to.deep.equal(expectedPayload);
});
But it seems that server.app.db and request.server.app.db are two different objects altogether. I tried monkey-patching server.root.app.db as well, but to no avail.
Is there a way to stub the db object, or do I need to use proxyquire to inject a fake database plugin?
I still haven't found the solution to the original problem, but instead, I am injecting a mock database plugin using Glue:
exports.register = (server, options, next) => {
// eslint-disable-next-line no-param-reassign
server.app.db = {
persistenceMethod: () => new Promise((resolve, reject) => {
resolve({foo: 'bar'});
})};
next();
};
I would prefer not touching the manifest for Glue, but it's the best I can do for now.