SpyOn express route handler function in controller - node.js

So i have a simple express route which I am trying to test using Jest. Below are the code snippets
auth.route.ts
import * as authController from '../controllers/auth.controller';
const router = express.Router();
// POST /api/user/register
router.post(`${userApis.register}`, authController.register);
export { router };
auth.route.test.ts
it('register', async() => {
const spy = jest.spyOn(authController, "register");
await request(app).post('/api/user/register');
expect(spy).toHaveBeenCalled();
});
But the test fails. Below is the error.
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
One way i got it working is re writing my route as below but that is not the right way i think
import * as authController from '../controllers/auth.controller';
const router = express.Router();
// POST /api/user/register
router.post(`${userApis.register}`, (req, res, next) => {
authController.register(req, res, next);
});
export { router };
What would be solution for this. I am new to Jest.
Thanks in Advance

* import object is read-only by specs and can fail to be spied or mocked depending on a specific setup.
router.post(`${userApis.register}`, authController.register)
line is evaluated on import, jest.spyOn in test scope is evaluated later and cannot affect it.
In case an import should be only spied and not mocked, this can be done for all tests in a module mock:
import * as authController from '../controllers/auth.controller';
jest.mock('../controllers/auth.controller', () => {
const controller = jest.requireActual('../controllers/auth.controller');
jest.spyOn(controller, "register");
return controller
});
That register call is asserted doesn't have a lot of value, asserting a response that is expected from this route handler would be enough. A request most likely causes side effects (database queries), they can be mocked or asserted instead.

Related

How to pass sequelize through express routes to controller in MVC model in node when using ES6 import/export?

I'm trying to refactor some existing code into an MVC model, and am not sure if I'm messing up the structure, or if I just can't figure out how to pass a variable, but, assuming my structure is good, how do I pass a sequelize instance through an Express route to a controller? Here's my code, hopefully simplified for clarity:
Structure:
src/
db.js
routes.js
server.js
controllers/mycontroller.js
models/mymodel.js
server.js:
'use strict';
import express from 'express';
import Sequelize from 'sequelize';
import { router as routes } from './routes';
import db from './db';
const app = express();
try {
await db.authenticate();
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
db.myTable.sync(); // this works fine
app.use(express.urlencoded({ extended: false }));
app.use(routes); // this, I think, needs to pass db.myTable
app.listen( 3300, () => {
console.log('Listening on 3300');
});
db.js:
'use strict';
import Sequelize from 'sequelize';
import myTableModel from './models/mymodel';
const sequelize = new Sequelize('sqlite::memory');
const db = {};
db.authenticate = () => sequelize.authenticate();
db.myTable = myTableModel(sequelize);
export default db;
routes.js:
import express from 'express';
export const router = express.Router();
import MyController from './controllers/mycontroller';
const myController = new MyController();
... // other routes elided for brevity
router.post('/test', myController.store); // that db.myTable I thought I needed to pass above,
// I think I need to pass again here. Or, alternatively, I could put a constructor into
// MyController and pass it as an arg above when I call 'new MyController', but I still have to
// get it down here into this routes file.
mycontroller.js:
'use strict';
import MyTableModel from '../models/mymodel'; // This was an experiment I tried, but in retrospect,
// it of course makes no sense. I don't need to import the model, I need to have passed the
// instantiated model down here somehow
export default class MyController {
store = async (req, res, next) => {
await MyTable.create({ full: req.body.fullUrl}); // This fails (of course), because
// MyTable.create doesn't exist here.
res.redirect('/');
}
}
So, back to the question: assuming this structure looks correct (feel free to comment on that as well), how do I get that MyTable sequelize object passed all the way through to the controller, so it can do its thing?
Maybe in calling directly the model ?
'use strict';
import { myTable } from '../db';
export default class MyController {
store = async (req, res, next) => {
await MyTable.create({ full: req.body.fullUrl});
res.redirect('/');
}
}

ES6 Dynamic Module Import Express Routes

I'm attempting to dynamically import files containing routes for an Express server using ES6 notation instead of associating each route with a file like this:
import router from './routes/test.mjs'
app.use('/api/create/test', router)
In my main file App.js I have a function which aims to return the matching :entity import from the query parameters:
async function routingFunct(req, res, next){
let entity=req.params.entity
const { default: dynamicImport } = await import(`./routes/${entity}.mjs`);
return dynamicImport
}
app.use('/api/create/:entity', routingFunct)
In my ./routes/test.mjs file I have
router.get('/', async function(req, res)
{
...await logic here...
})
export default router
When sending a get request to the endpoint /api/create/test, the page simply hangs forever. What am I doing wrong here? How can I tell my Express server to run the middleware in App.js then run the logic in the matching :entity.mjs?

new to backend- .json formatting data

Hey guys I have been coding a little over a year now and focused almost entirely on front end (js, BUNCHA frameworks, CSS, html, etc etc)
it wasn't until literally yesterday that I came across the infamous CORS issue with spotify API.
while i have finished a udemy course that taught me a good deal of node.js/express i really didn't understand how to combine it all with front-end (react) until the other day. i started practicing
fetching data from the pokemon API before going to spotify simply because it is much easier to do (lol)... but i also know i can use express to enable cors etc. for the spotify app. ALL THAT ASIDE my question is essentially about parsing data into json using the simple .json() method.
how come in my backend (where i fetch the results) i have to use data.json() and then AGAIN i have to do it all? how come i also have to reimplement the async/await stuff in both iterations when it should all be done in the initial fetchdata function?
here is my fetchData.js file ---- where i initially call to the API
import fetch from "node-fetch"
const fetchData=async()=>{
const data=await fetch('https://pokeapi.co/api/v2/ability')
const res=await data.json()
const final=res.results
return res.results
}
export default fetchData
here is my server.js file where i call that function and send the data over when i client requests something from the path '/api/data'
import express from 'express';
const app=express()
import axios from 'axios'
import cors from 'cors'
import fetchData from './fetchData.js'
app.use(cors())
app.get('/api/data', async (req, res)=>{
const data=await fetchData()
res.send(data)
})
app.listen(3000, ()=>{
console.log('listening on port 3000')
})
and lastly here is my react component which makes a call to that path using the retrieval() function... how come i have to keep repeating async await logic AND .json() calls when the data seems to already be parsed and ready to go in the fetchData() function?
import React, {useState, useEffect} from 'react'
const Customers = () => {
const [pokemon, setPokemon]=useState([])
useEffect(()=>{
const retrieval=async()=>{
const data=await fetch('api/data')
const res=await data.json()
setPokemon(res)
}
retrieval()
console.log(pokemon)
},[])
return (
<div>
<ul>{pokemon.map(poke=>(
<li>{poke.name}</li>
))}</ul>
</div>
)
}
i guess if someone could just walk me through the whole process in layman's terms. im pretty decent with front-end, ui, and react etc.. but connecting backend is totally new to me and tbh the json.stringify(), json.parse(), and .json() stuff always confuses me... i usually just guess until it works haha. thanks in advance...
The reason you need to call .json() each time, is because you are actually making two API calls.
The first call is in the fetchData() function, where you are calling the Pokemon API.
The second call is in your retrieval() function, where you are actually calling your own API.
This is standard industry practice, and is always recommended to make calls to external APIs by way of a custom API endpoint from the frontend, instead of directly calling the external API from the frontend.
As a side note, you could easily utilize axios, which you seem to already have imported, to make the code a bit cleaner.
fetchData.js
import axios from 'axios';
const fetchData = async () => {
const response = await axios.get('https://pokeapi.co/api/v2/ability');
return response.data;
};
export default fetchData;
server.js
import express from 'express';
const app = express();
import cors from 'cors';
import fetchData from './fetchData.js';
app.use(cors());
app.get('/api/data', async (req, res) => {
const data = await fetchData();
return res.send(data);
});
app.listen(3000, () => {
console.log('listening on port 3000');
});
page.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Customers = () => {
const [pokemon, setPokemon] = useState([]);
useEffect(() => {
const retrieval = async () => {
const response = await axios.get('http://localhost:3000/api/data');
setPokemon(response.data);
};
retrieval();
console.log(pokemon);
}, []);
return (
<div>
<ul>{pokemon.map(poke => (
<li>{poke.name}</li>
))}</ul>
</div>
);
};

Jest with Typescript error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout

I'm studying how to create some tests using the Jest with Nodejs, i'm actually using typescript.
When I try to run a simple test, by checking the status of the response, it shows the following error:
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
What could I do?
Here's my following codes:
session.test.ts =>
const request = require('supertest');
import app from '../../src/server';
describe('Authentication',() => {
it('should authenticate with valid credentials',async() =>{
const response = await request(app)
.post('/sessions')
.send({
email: "myemail#gmail.com",
password: "123456"
})
await expect(response.status).toBe(200);
});
});
SessionController.ts =>
import {Request, Response} from 'express';
export default class SessionController{
async store(request: Request, response: Response){
return response.status(200);
}
}
server.ts =>
import express from 'express';
import routes from './routes';
require("dotenv").config({
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env"
});
const app = express();
app.use(express.json());
app.use(routes);
app.listen(3333);
export default app;
and routes.ts:
import express from 'express';
import UsersController from './controllers/UsersController';
import SessionController from './controllers/SessionController';
const routes = express.Router();
const usersControllers = new UsersController();
const sessionController = new SessionController();
routes.post('/users',usersControllers.create);
routes.post('/sessions',sessionController.store);
export default routes;
at my SessionController.ts, I had to put the following:
import {Request, Response} from 'express';
export default class SessionController{
async store(request: Request, response: Response){
return response.status(200).send()
}
}
I forgot to send haha
The first thing is to check if there are some error in the request, or (more likely) if it remain in a pending state, because 5 seconds are tons of time.
Anyway you can specify the test timeout like follow
describe('Authentication',() => {
it('foobar', async function () { // no arrow function
this.timeout(10000)
await myFunc()
});
});
I am not sure you are actually 'completing' the request using the supertest API.
The fluent chaining approach of supertest allows you to carry on adding 'steps' to the HTTP request before actually dispatching it. Unfortunately, send() is preparing a send step for when you dispatch. It doesn't actually dispatch as you can see from this superagent example in which many further configuration steps follow send() and it's only end() which runs them all.
In several supertest examples I saw there is a chained 'expect' call which would presumably also trigger the actual HTTP post.
Equally, the 'end()' docs at https://github.com/visionmedia/supertest#endfn say...
Perform the request and invoke fn(err, res)
This indicates to me that until you send a 'finalising' call, there won't be a request.

Is it possible to call Express Router directly from code with a "fake" request?

Tangential to this question, I would like to find out if there is a way of triggering the Express Router without actually going through HTTP?
The Router has a "private" method named handle that accepts a request, a response, and a callback. You can take a look at the tests that Express has for its Router. One example is:
it('should support .use of other routers', function(done){
var router = new Router();
var another = new Router();
another.get('/bar', function(req, res){
res.end();
});
router.use('/foo', another);
router.handle({ url: '/foo/bar', method: 'GET' }, { end: done });
});
The Express team uses SuperTest to perform integration tests on the Router. It is my understanding that SuperTest still uses the network but they handle all of this for you so it behaves as if the tests were all in memory. SuperTest does seem to be widely used and an acceptable way to test your routes.
As an aside, you didn't say what you were attempting to test but if your goal is to test some routes, an alternative to SuperTest could be to extract the logic in your routes into a separate module that can be tested independent of Express.
change:
routes
|
-- index.js
to:
routes
|
-- index.js
|
controllers
|
-- myCustomController.js
The tests could then simply target myCustomController.js and inject any necessary dependencies.
By going to the source of Express, I was able to find out that there is indeed an API that is just as simple as I wished for. It is documented in the tests for express.Router.
/**
* #param {express.Router} router
*/
function dispatchToRouter(router, url, callback) {
var request = {
url : url,
method : 'GET'
};
// stub a Response object with a (relevant) subset of the needed
// methods, such as .json(), .status(), .send(), .end(), ...
var response = {
json : function(results) {
callback(results);
}
};
router.handle(request, response, function(err) {
console.log('These errors happened during processing: ', err);
});
}
But ... the downside is, exactly the reason why it is undocumented in the first place: it is a private function of Router.prototype:
/**
* Dispatch a req, res into the router.
* #private
*/
proto.handle = function handle(req, res, out) {
var self = this;
...
}
So relying on this code is not the safest thing in the world.
You can use run-middleware module exactly for that. You create an express app a usuaul, and then you can call the app using your parameters
it('should support .use of other routers', function(done){
var app=require('express')()
app.get('/bar', function(req, res){
res.status(200).end();
});
app.runMiddleware('/bar',{options},function(responseCode,body,headers){
console.log(responseCode) // Should return 200
done()
})
});
More info:
Module page in Github & NPM;
Examples of use run-middleware module
Disclosure: I am the maintainer & first developer of this module.

Resources