Test NestJs API controller with Jest - api

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.

Related

Test Express.js routes which rely on a TypeORM connection

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" });
});
});

Jest / Supertest Error - Right-hand side of 'instanceof' is not callable

When using supertest like so,
import app from "../../src/app";
import request from "supertest";
describe("GET / - a simple api endpoint", () => {
it("Hello API Request", () => {
const result = request(app)
.get("/api/location/5eda6d195dd81b21a056bedb")
.then((res) => {
console.log(res);
})
// expect(result.text).toEqual("hello");
// expect(result.status).toEqual(200);
});
});
Im getting "Right-hand side of 'instanceof' is not callable".
at Response.toError (node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (node_modules/superagent/lib/response-base.js:123:16)
at new Response (node_modules/superagent/lib/node/response.js:41:8)
at Test.Request._emitResponse (node_modules/superagent/lib/node/index.js:752:20)
at node_modules/superagent/lib/node/index.js:916:38
at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/parsers/json.js:19:7)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
status: 500,
text: `"Right-hand side of 'instanceof' is not callable"`,
method: 'GET',
path: '/api/location/5eda6d195dd81b21a056bedb'
This is just with supertest, the API works when using Postman.
Rest of the code for this call,
router.get(
"/location/:id",
(req, res) => {
locationController.getLocation(req, res);
}
);
const getLocation = async (req: Request, res: Response): Promise<void> => {
const { id } = req.params;
const location = await data.readRecord(id, Location);
res.status(location.code).json(location.data);
};
const readRecord = async (id: string, model: IModel): Promise<Response> => {
try {
const response = await model.findById(id);
if (response == null) return { code: 404, data: `ID ${id} Not Found` };
return { code: 200, data: response };
} catch (error) {
return errorHandler(error);
}
};
Is there a configuration im missing for supertest and typescript?
This approach worked,
import request = require("supertest");
import app from "../../src/app";
describe("GET/ api/location/id", () => {
it("should connect retrieve record and retrieve a code 200 and json response", async () => {
const res = await request(app)
.get(`/api/location/${id}`)
expect(res.status).toBe(200);
expect(res.body._id).toBe(`${id}`);
});
});
If you don't want to use "await" in your code , you can use "done()" in callback function.
like this.
import app from "../../src/app";
import request from "supertest";
describe("GET / - a simple api endpoint", () => {
it("Hello API Request", (done) => {
const result = request(app)
.get("/api/location/5eda6d195dd81b21a056bedb")
.then((res) => {
console.log(res);
expect(res.text).toEqual("hello");
expect(res.status).toEqual(200);
done();
//done() function means this test is done.
})
});
});
Awaiting the expect call (with Jest) worked for me.
await expect(...).rejects.toThrow()

How to write business logic in a service as sinon in ExpressJS

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);
});

React Native variable getting undefined before running method

I have a function that calls a method that is in my Helper.js file.
import { getTest } from '../../common/Helper';
...
myMethod() {
...
const test = getTest(this.state.myID);
console.log(test);
}
...
My Helper.js:
export const getTest = (pID) => {
axios.get('http://myserver.com/', {
params: {
method: 'getVacantUnits',
propertyID: pID
}
}).then((response) => {
console.log(response.data);
return response.data;
}).catch((error) => {
// handle error
console.log(error);
return 0;
});
};
It is odd because my output is:
undefined
myDataContent
It looks like that "const test" is receiving undefined before the getTest being run. Why is it happening?
Thanks
It's returning this first since it's not awaiting the result:
console.log(test);
2 easy ways to fix this I am showing below, first with promise:
const test = getTest(this.state.myID).then(response=> console.log(response)).catch(err => console.log(err))
Add in return as well since you need to return from outermost function
export const getTest = (pID) => {
return axios.get('http://myserver.com/', {
params: {
method: 'getVacantUnits',
propertyID: pID
}
}).then((response) => {
console.log(response.data);
return response.data;
}).catch((error) => {
// handle error
console.log(error);
return 0;
});
};
second using async await:
// add in await
export const getTest = async (pID) => {
return axios.get('http://myserver.com/', {
params: {
method: 'getVacantUnits',
propertyID: pID
}
}).then((response) => {
console.log(response.data);
return response.data;
}).catch((error) => {
// handle error
console.log(error);
return 0;
});
};
// here you are awaiting the response before you run console.log
const test = await getTest(this.state.myID);
console.log(test);
You can solve this in several other ways, but I think these are the 2 easiest. Basically think about the fact that those are run synchronously and the console.log executes before the function returns, so if you "wait" then it makes it so the console.log() is dependent on the first function executing first.

How to test Axios reject condition using Jest

I wrote a unit test for some Axios calls in my component. I verified the success path, where the call resolves successfully, but I am not able to verify the failure path, where the call rejects. How do I use mocks to verify this?
Here's a snippet of my FetchImage.vue component:
methods: {
preparedFetch() {
axios.get(this.imageurl).then(result => {
this.imageInformation.title = result.data.title;
this.imageInformation.copyright = result.data.copyright;
this.imageInformation.detailExplanation = result.data.explanation;
this.imageInformation.date = result.data.date;
this.imageInformation.urlinfo = result.data.url;
this.resultArrived = true;
this.$emit('imagefetched',this.imageInformation);
})
.catch( error => {
this.errorMessage = "Information not found";
this.resultArrived = true;
});
}
}
And my test for when the call rejects (for an invalid URL):
describe('Invalid response',async () => {
beforeEach(() => {
axios.get.mockClear();
axios.get.mockReturnValue(Promise.reject({}));
});
it('Invalid URL verfication', async () => {
// Given
const result = {
errorMessage : "Information not found",
resultArrived : true,
fetchStatus : true
};
// Fetch the error result
axios.get.mockReturnValue(Promise.resolve(result));
const fetchwrapper = mount(FetchImage);
fetchwrapper.vm.imageurl = "https://invalid.request.gov";
fetchwrapper.vm.preparedFetch();
await fetchwrapper.vm.$nextTick();
// Validate the result
expect(axios.get).not.toHaveBeenCalledWith('https://api.nasa.gov/planetary/apod?api_key=vME6LAMD7IhEiy7rDmjfIaG6MhiKbu1MNIqxtqd1');
expect(axios.get).toHaveBeenCalledWith("https://invalid.request.gov");
expect(axios.get).toHaveBeenCalledTimes(1);
expect(fetchwrapper.vm.errorMessage.length).not.toEqual(0);
expect(fetchwrapper.vm.errorMessage).toBe("Information not found");
});
});
Your catch block isn't running because the mock return value is using Promise.resolve() when it actually should be Promise.reject():
describe('Invalid response',async () => {
it('Invalid URL verfication', async () => {
// axios.get.mockReturnValue(Promise.resolve(result)); // DON'T DO THIS
axios.get.mockReturnValue(Promise.reject(result));
});
});
You have to reject the value by using the built-in jest method.
describe('Invalid response', async () => {
it('Invalid URL verfication', async () => {
axios.get.mockRejectedValue(result);
});
});