I want to check whether "res.render" is called with the correct arguments.
it("Renders the page where a new user can be created", done => {
const spy = sinon.spy(ejs, "render");
chai
.request(app)
.get("/users/create")
.end((err, res) => {
res.should.have.status(200);
spy.should.have.been.calledOnce;
done();
});
});
Even thought the render function gets called, the test report tells me that render function is never called.
Uncaught AssertionError: expected render to have been called exactly once, but it was called 0 times
How can I spy on a ejs render function that is called on the response object and assert whether it is called with the correct arguments?
EDIT: Our project is using express-ejs-layouts to render ejs files
If you call res.render() using EJS, it's not ejs.render() that gets called but ejs.renderFile() (source).
Following the code path for that method, it never calls ejs.render().
Spying on ejs.renderFile() is also not possible, because of how Sinon works (spying on ejs.renderFile replaces the exported reference to the renderFile method, but ejs uses the internal reference).
However, you can spy on ejs.__express, which is the function that Express will call for res.render():
const spy = sinon.spy(ejs, "__express");
EDIT: I now realise that you want to make sure if res.render() is being called with the correct arguments, so it would make more sense to spy on that method:
const { response } = require('express');
...
const spy = sinon.spy(response, 'render');
Related
I have a test that is failing even though I am calling it. I am spying on the PrimeNG Message Service.
Below is some code that I have so far.
it('should throw error', () => {
const mockCall = spyOn(service, 'update$').and.throwError('Error');
const msgService = spyOn(messageService, 'add').and.callThrough();
expect(mockCall).toThrowError('Error');
expect(msgService).toHaveBeenCalled();
});
I am expecting this to pass this test since it gets called withing my update$ observable if there is an error. Here is the error
"Expected spy add to have been called"
As a rule of thumb you should not apply mocks to the method you are testing. You should be calling the method you are testing directly, then verify what happened. You should also avoid verifying your mocks. You've set up service.update$ to throw, that's what it will do, you do not need to verify it happened.
Your test should probably look something like this:
it('should throw error', () => {
// Set up
const mockCall = spyOn(service, 'update$').and.throwError('Error');
// Test
const message = {};
messageService.add(message);
// Verify
expect(mockCall).toHaveBeenCalledWith(message);
});
Is there a way to call fetch in a Jest test? I just want to call the live API to make sure it is still working. If there are 500 errors or the data is not what I expect than the test should report that.
I noticed that using request from the http module doesn't work. Calling fetch, like I normally do in the code that is not for testing, will give an error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout. The API returns in less than a second when I call it in the browser. I use approximately the following to conduct the test but I also have simply returned the fetch function from within the test without using done with a similar lack of success:
import { JestEnvironment } from "#jest/environment";
import 'isomorphic-fetch';
import { request, } from "http";
jest.mock('../MY-API');
describe('tests of score structuring and display', () => {
test('call API - happy path', (done) => {
fetch(API).then(
res => res.json()
).then(res => {
expect(Array.isArray(response)).toBe(true);
console.log(`success: ${success}`);
done();
}).catch(reason => {
console.log(`reason: ${reason}`);
expect(reason).not.toBeTruthy();
done();
});
});
});
Oddly, there is an error message I can see as a console message after the timeout is reached: reason: ReferenceError: XMLHttpRequest is not defined
How can I make an actual, not a mocked, call to a live API in a Jest test? Is that simply prohibited? I don't see why this would fail given the documentation so I suspect there is something that is implicitly imported in React-Native that must be explicitly imported in a Jest test to make the fetch or request function work.
Putting aside any discussion about whether making actual network calls in unit tests is best practice...
There's no reason why you couldn't do it.
Here is a simple working example that pulls data from JSONPlaceholder:
import 'isomorphic-fetch';
test('real fetch call', async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/1');
const result = await res.json();
expect(result.name).toBe('Leanne Graham'); // Success!
});
With all the work Jest does behind the scenes (defines globals like describe, beforeAll, test, etc., routes code files to transpilers, handles module caching and mocking, etc.) ultimately the actual tests are just JavaScript code and Jest just runs whatever JavaScript code it finds, so there really aren't any limitations on what you can run within your unit tests.
I'm using a router middleware to check if the request is valid or not. Also, I'm using a global Error handler middleware at the end of the server.js to catch and process all the errors.
Inside the router, I'm facing a problem implementing this. The following code will state the problem clearly.
Error middleware errorHandler.js
module.exports = function(err, req, res, next) {
//some logic
return res.status(400 or 500).json({err});
};
My server.js (short version)
const express = require('express');
const app = express();
app.use('/account/:accid/users', userRouteHandler);
const errorHandler = require('PATH to errorHandler.js file');
app.use(errorHandler);
app.listen(3000, () => console.log(`Example app listening on port 3000!`));
userRouteHandler file
var express = require('express');
var router = express.Router({ mergeParams: true });
router.use(express.json());
// middleware to use for all requests
router.use(function(req, res, next) {
/**** PROBLEM / CONFUSION ****/
checkIfAccountIdExists(req.params.accid) //if exists resolve(count) else reject(new Error)
.then(next) //I want to allow calling the get if it exists
.catch(next); //for error it should call the global error handler
});
router
.get('/', function(req,res,next){
console.log("entering here");
findAllTheUsers({accid:req.params.accid})
.then(users=>res.status(200).json(users);
.catch(err=>next(err)); //ensure it can call the global error handler
});
The code always calling the get route. As for both, I'm calling next(). If I call next() only inside then(), my global error handler will get skipped if any error occurs.
One way could be directly calling my errorHanler function within the catch block. but I want to keep my code separate and don't really want to require my errorHandler file within each route.
How can I achieve this?
Without knowing the behavior of your checkIfAccountIdExists function, my supposition is that it returns a promise that always resolves with false|undefined|null; something like this:
checkIfAccountIdExists(id).then((exists) => console.log(exists));
// outputs "false" or "undefined" or "null"
It's my supposition, because otherwise your .get('/') route shouldn't even enter, given how next() works.
Understanding next():
Calling next() (to the shame of Express) has always been confusing without having in-depth knowledge of it. It basically works in 3 ways:
next() (no arguments) -> pass execution to next callbacks in the
route
next('route') (the string 'route' argument) -> bypass any remaining callbacks in the route (moves into any routes that follow)
next(err) (any other truthy parameters, aside from 'route') -> invokes the error handlers.
In your specific case, my assumption is that checkIfAccountIdExists() solves with false|undefined|null, essentially invoking next(err) signature, but because err is not truthy, it's treated as a next() signature, moving onto the next route callback. I'd check the sanity of checkIfAccountIdExists() for this.
Using next() with Promises
When using Promises, it's important to remember that your first argument of .then() (meaning your fulfillment handler) will always receive a parameter!
promise.then(callback);
function callback() {
console.log(arguments.length); // ALWAYS 1
}
For this reason, you should always avoid setting next() as a fulfillment handler to promises. Otherwise, once your checkIfAccountIdExists() will solve with true, it will actually invoke the next(err) signature!
Always write: promise.then(() => next()) instead of promise.then(next), to make sure you call next without arguments.
Writing promise.catch(next) is however fine, because it's the same as promise.catch((err) => next(err))
A bit more info on Promises
Also, Promises (thenables) allow for two arguments on .then(), one being a fulfillment handler and one a rejection handler.
Example: promise.then(onFulfillment, onRejection) which is similar to calling promise.then(onFulfillment).catch(onRejection) except of how errors are caught!
For .then(onFulfillment, onRejection) any errors that are thrown
inside onFulfillment are never caught by onRejection.
For .then(onFulfillment).catch(onRejection) any errors that are thrown
inside onFulfillment are also caught by onRejection
In your case, that means you can safely write
checkIfAccountIdExists.then(() => next(), next);
because the next route (onFulfillment arg) will also handle errors.
Note: errors inside synchronous code will be caught by express.
More info:
Express Error Handling
Promise.prototype.then()
I use mongoose and I have login function, that tries to find user and then valid his password (I will not describe here all logic of this function, but only major parts to show you my problem).
function login(req, res) {
...
User.findOne(...)
.then((user) => {
user.validPassword(...);
...
});
}
I have defined a User model that contains a validPassword method and all work fine but I have trouble to spy validPassword method.
I use Jasmine to my tests and I tried to do this in this way:
const user = new User(...);
spyOn(user, 'validPassword').and.callThrough();
expect(user.validPassword).toHaveBeenCalled(); // was never called :(
And of course I called login function. When I test findOne method in this way, it works fine, but it is simpler because I call method from User constructor:
spyOn(User, 'findOne').and.callThrough();
expect(User.findOne).toHaveBeenCalled(); // this works fine!
I think my problem is related to different instances, because findOne method resolve a new user object for me and in the tests I create a second one, what is absolutely different object, but I'm not sure about this conjecture.
Can you tell me how to repair this?
Ok I fixed this.
1) I found solution of my problem here
2) I noticed that findOne method returns promise and then I do async task, so I had to check it with 'done' function before start testing.
function login(req, res) {
...
return User.findOne(...)
.then((user) => {
user.validPassword(...);
...
});
}
In jasmine:
beforeAll((done) => {
spyOn(User.prototype, 'validPassword').and.callThrough();
login(...).then(() => done());
}
it('calls validPassword', () => {
expect(User.prototype.validPassword).toHaveBeenCalled(); // works :)
});
I started to istanbul as a test coverage tool with mocha and one of the great things is that it shows you the paths (branches) you have tested in your test code logic.
There is a path that can only be taken if a error occurs on the database.
Screenshot of the part I am interested in testing
The [I] indicates the the first if was not tested.
The problem is that it uses a callback function(err, data) and the error is passed to this callback through a the mongoose model method find(), and because of that I don't have the flow control of this part of the code.
In this specific case I using supertest which is a module to test routes in node.js and it make requests to a route that calls a mongoose model method find().
What would be the best option to test this path? Create a stub to simulate the method? Or just remove the if?
EDIT: I noticed that I was using an anonymous function as a callback (err, data) and doing so I can't test it since it's not exposed to the outer scope. One approach I had in mind was to create a function:
handleDbFetchingResponse(res) {
return function(err, data) {
let response = {};
if (err) {
response = {error: true, message: 'Error fetching data'};
} else {
response = {error: false, message: data};
}
res.json(response);
}
}
Now I can expose the function and test it, I create another problem though. Since the other express routes have another logic when fetching data from the database I will have to create a handler function for each one of them. Maybe there is a way to create a handlerBuilder function that returns a new handler passing different arguments to deal with specific cases.