Making my first express app, I am trying to write a test for an api endpoint and using data structure as place-holder for a database but keep getting an error in the console as shown in the pic even though the test 'passes'
import chai from 'chai';
import { app } from '../app';
import http from 'chai-http';
let expect = chai.expect;
//test I am having problems with
it('should get a single todo at "/api/v1/todos/:id" with GET/id', () => {
return chai
.request(app)
.post('/api/v1/todos')
.send({ title: 'Breakfast', description: 'Breakfast alone' })
.then(resPost => {
expect(resPost).to.have.status(201);
chai
.request(app)
.get('/api/v1/todos/2')
.then(res => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body)
.to.have.property('success')
.eql('true');
expect(res.body)
.to.have.property('message')
.equal('todo retrieved successfully');
expect(res.body.todo).to.be.an('object');
expect(res.body.todo)
.to.have.property('id')
.equal(1);
expect(res.body.todo)
.to.have.property('title')
.equal('Breakfast');
expect(res.body.todo)
.to.have.property('description')
.equal('Breakfast alone');
});
});
});
//api endpoints
//import and define express framework
import express from 'express';
import todoController from '../todoControllers/todo';
//create router handler
const router = express.Router();
router.get('/api/v1/todos', todoController.getAllTodos);
router.get('api/v1/todos/:id', todoController.getTodo);
router.post('/api/v1/todos', todoController.createTodo);
router.put('/api/v1/todos/:id', todoController.updateTodo);
router.delete('/api/v1/todos/:id', todoController.deleteTodo);
export default router;
//controllers for api endpoints
import db from '../db/db';
class todosController {
getAllTodos(req, res) {
return res.status(200).send({
success: 'true',
message: 'todos retrieved successfully',
todos: db
});
}
getTodo(req, res) {
const id = parseInt(req.params.id, 10);
db.map(todo => {
if (todo.id === id) {
return res.status(200).send({
success: 'true',
message: 'todo retrieved successfully',
todo
});
}
});
return res.status(400).send({
success: 'false',
message: 'todo does not exist'
});
}
createTodo(req, res) {
if (!req.body.title) {
return res.status(400).send({
success: 'false',
message: 'title is required'
});
} else if (!req.body.description) {
return res.status(400).send({
success: 'false',
message: 'description is required'
});
}
const todo = {
id: db.length + 1,
title: req.body.title,
description: req.body.description
};
db.push(todo);
return res.status(201).send({
success: 'true',
message: 'todo added successfully',
todo
});
}
updateTodo(req, res) {
const id = parseInt(req.params.id, 10);
let todoFound;
let itemIndex;
db.map((todo, index) => {
if (todo.id === id) {
todoFound = todo;
itemIndex = index;
}
});
if (!todoFound) {
return res.status(404).send({
success: 'true',
message: 'todo not found'
});
}
if (!req.body.title) {
return res.status(400).send({
success: 'false',
message: 'title is required'
});
} else if (!req.body.description) {
return res.status(400).send({
success: 'false',
message: 'description is required'
});
}
const editedTodo = {
// @ts-ignore
id: todoFound.id,
// @ts-ignore
title: req.body.title || todoFound.title,
// @ts-ignore
description: req.body.description || todoFound.description
};
db.splice(itemIndex, 1, editedTodo);
return res.status(200).send({
success: 'true',
message: 'todo updated successfully',
editedTodo
});
}
deleteTodo(req, res) {
const id = parseInt(req.params.id, 10);
let todoFound;
let itemIndex;
db.map((todo, index) => {
if (todo.id === id) {
todoFound = todo;
itemIndex = index;
}
});
if (!todoFound) {
return res.status(404).send({
success: 'true',
message: 'todo not found'
});
}
db.splice(itemIndex, 1);
return res.status(200).send({
success: 'true',
message: 'todo deleted successfully'
});
}
}
const todoController = new todosController();
export default todoController;
//place-holder database in the form of datasctructures
const todos = [
{
id: 1,
title: 'lunch',
description: 'Go for lunch by 2pm'
}
];
//Creating module from data structure and exposing it to be used by parts of the server
export default todos;
//tests written so far including the initial snippet
import chai from 'chai';
import { app } from '../app';
import http from 'chai-http';
import db from '../db/db';
let expect = chai.expect;
chai.use(http);
describe('Test all todo endpoints at "/api/v1/todos and "/api/v1/todo/:id" with (GET, POST, GET/id, PUT)', () => {
before(() => {});
after(() => {});
//GET all todos
it('should get all todos at "/ap1/v1/todos" with GET', () => {
return chai
.request(app)
.get('/api/v1/todos')
.then(res => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body)
.to.have.property('success')
.eql('true');
expect(res.body)
.to.have.property('message')
.eql('todos retrieved successfully');
expect(res.body.todos).to.be.an('array');
expect(
res.body.todos[Math.floor(Math.random() * res.body.todos.length)]
).to.have.property('id' && 'title' && 'description');
});
});
//POST a todo
it('should add a todo at "/api/v1/todos" with POST', () => {
return chai
.request(app)
.post('/api/v1/todos')
.send({ title: 'Dinner', description: 'Dinner with bae' })
.then(res => {
expect(res).to.have.status(201);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body)
.to.have.property('success')
.eql('true');
expect(res.body)
.to.have.property('message')
.equal('todo added successfully');
expect(res.body.todo).to.be.an('object');
expect(res.body.todo)
.to.have.property('id')
.equal(db.length);
expect(res.body.todo)
.to.have.property('title')
.equal('Dinner');
expect(res.body.todo)
.to.have.property('description')
.equal('Dinner with bae');
});
});
//corrected test still giving an 'AssertionError'
it('should get a single todo at "/api/v1/todos/:id" with GET/id', () => {
return chai
.request(app)
.post('/api/v1/todos')
.send({ title: 'Breakfast', description: 'Breakfast alone' })
.then(resPost => {
expect(resPost).to.have.status(201);
return chai
.request(app)
.get('/api/v1/todos/2')
.then(res => {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body)
.to.have.property('success')
.eql('true');
expect(res.body)
.to.have.property('message')
.equal('todo retrieved successfully');
expect(res.body.todo).to.be.an('object');
expect(res.body.todo)
.to.have.property('id')
.equal(1);
expect(res.body.todo)
.to.have.property('title')
.equal('Breakfast');
expect(res.body.todo)
.to.have.property('description')
.equal('Breakfast alone');
});
});
});
});
can someone explain why? P.S : I updated the question to show my api endpoints, tests and place-holder for database, I essentially need hints on how to handle requests that involve and id.
You have two problems:
This is because in line 14 you are missing a return
. Since the function you register in line 12 doesn't return anything, the promise resolves. As a result, the promise you return in line 8 also resolves and your test doesn't fail although it should.
Since you do not return the rejected promise the error isn't propagated. That's why you get the error message.
You are creating a resource and then requesting this resource assuming that its id is 2. You are getting a 404 response, which means that either the resource wasn't created at all or it has a different id.
When creating the resource you assign it an id in some way (it looks like you're just using a counter for this, but I can't tell for sure without seeing your implementation). A POST response should provide some information about this id. Ideally, it sends the id as the Location header, but often the complete created resource is sent as the response body. So before making the GET request, you need to extract the id from the POST response.