Mocking express with jest? - node.js

Am pretty new to JS, but I am trying my best to learn. As it is, I am trying to mock express. Here is my base class (cut down, for testing purposes):
import compression from 'compression';
import express from 'express';
export default class Index{
constructor(){}
spawnServer(){
console.log(express());
let app = express();
app.use(STATIC_PATH, express.static('dist'));
app.use(STATIC_PATH, express.static('public'));
etc...
}
}
And here is the test I am trying to achieve, in a separate test file...:
test('should invoke express once', () =>{
index.spawnServer();
expect(mockExpressFuncs().use.mock.calls.length).toBe(3);
})
My question is - how do I make the test override the require of the class under test - is that even possible? I want to have my Index use a mocked version of express, one that includes express() and express.require.
I did read through the documentation, and attempted something like:
const mockFunction = function() {
return {
use: useFn,
listen: jest.fn()
};
};
beforeEach(() => {
jest.mock('express', () => {
return mockFunction;
})
express = require('express');
});
But that did not work - what am I doing wrong? :(
Thanks.

Create the mock app object and make it be returned by express module.
Then you can check how many times app.use have been called using either expect(app.use.mock.calls.length).toBe(3) or better expect(app.use).toHaveBeenCalledTimes(1)
const app = {
use: jest.fn(),
listen: jest.fn()
}
jest.doMock('express', () => {
return () => {
return app
}
})
test('should invoke express once', () => {
expect(app.use).toHaveBeenCalledTimes(1)
})

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('/');
}
}

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

Problems running mocha on Firebase functions

I wrote a Node/Typescript app that used data from Firebase Cloud Firestore. The app worked perfectly and I was able to test my endpoints fine using simple mocha commands of the generated .js file. Here is an example of one of the test scripts:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', () => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200)
)
it('does not allow put at top level', () =>
supertest(app)
.put('/risks')
.expect(403)
)
})
Here is the App.js that is referring to (imports and declarations excluded):
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
Here is the Risk router with only the GET endpoint:
const express = require('express');
const bodyParser = require('body-parser');
const riskRouter = express.Router();
import { firestore, firebasestore } from '../firebase/firebase';
riskRouter.use(bodyParser.json());
riskRouter.route('/')
.get((req,res,next) => {
return firestore.collection('risks').get()
.then(snapshot => {
let risks = [];
snapshot.forEach(doc => {
const data = doc.data()
const _id = doc.id
risks.push({_id, ...data });
});
res.send(risks)
})
.catch( err => res.json({error: err}))
})
// POST, PUT and DELETE are implemented here but not needed for this discussion
module.exports = riskRouter
When I tried to migrate this to Firebase, I basically copied the entire node application to the /functions directory and made the following change to the App.ts file
let riskRouter = require('./routes/Risk')
class App {
public express
constructor () {
this.express = express()
this.mountRoutes()
}
private mountRoutes (): void {
const router = express.Router()
router.get('/', (req, res) => {
res.json({
message: 'Hello World!'
})
})
this.express.use(bodyParser.json());
this.express.use(cors({ origin: true }))
this.express.use('/', router)
this.express.use('/risks', functions.https.onRequest(riskRouter))
}
}
export default new App().express
In both cases, the test command in package.json is
"test": "tsc && mocha lib/**/*.spec.js"
Also, the Risk router is identical in both cases.
In the case that works, all the test simply run cleanly. Also, they are making calls to the external Firebase backend
In the case that fails, I get the following output:
Risk API
1) works to get /
0 passing (2s)
1 failing
1) Risk API works to get /:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
All of my endpoints use some form of res.send(), res.json(), etc which I read is sufficient and the explicit use of done() is not needed. If I'm wrong about that, I'd like to know as well as the proper syntax to fix it.
I also tried running mocha directly on the generated test script using the --timeout 15000 option, but got the same result.
Any suggestions would be greatly appreciated!
Can you try the following:
import * as supertest from 'supertest'
import app from '../App'
describe('Risk API', (done) => {
it('works to get /', () =>
supertest(app)
.get('/risks')
.expect('Content-Type', /json/)
.expect(200, done)
)
it('does not allow put at top level', (done) =>
supertest(app)
.put('/risks')
.expect(403, done)
)
})

How can I get react-router v4 defined params with express at server-side

I try to get the :userId "albert" from this url
http://localhost:5000/search/albert?query=al&page=1
at server side but failed, what can I do to get the react-router defined params correctly at node.js with express?
routes.js
[
{
path: '/search/:userId',
component: Search,
}, {
path: '/search',
component: Search,
}
...
]
server.js
server.get('*', async (req, res, next) => {
const pageData = await routes
.filter(route => matchPath(req.path, route))
.map((route) => {
console.log(route)
return route.component
})
}
The React-Router Way
React Router V4 does include a way to extract param data server-side using their matchPath() function, using their standard parameter implementation, "/path-name/:param" route matching.
In this case, it allows me to do a lot of server-side stuff based on the parameter before the express app responds with the page data.
NOTE: this is probably not the most basic implementation, but it's a pared down version of my complete SSR react implementation that makes use of matchPath().
Requirements
Server-side rendered react app
React-router-dom v4
Centralized routes file (because SSR)
Express app server (I'm hosting my express app on Firebase)
In This Example, a server-side express app attempts to run an "initialAction" function in each component during a fresh page load. It passes promise resolve and reject to know when the function is completed running, and the request object which may contain useful params we can extract with matchPath(). It does this for every matching route, again, using matchPath().
Routes.js Example
Where :id is the "id" param in the URL.
const routes = [
{
path: "/news-feed/:id",
component: NewsFeed,
exact: true
},
]
export default routes;
Component Example
Just showing the initialAction() function in the component
import { Link, matchPath } from 'react-router-dom';
class NewsFeed extends Component {
// Server always passes ability to resolve, reject in the initial action
// for async data requirements. req object always passed from express to
// the initial action.
static initialAction(resolve, reject, req) {
function getRouteData() {
let matchingRoute = routes.find(route => {
return matchPath(req.path, route);
});
console.log("Matching Route: ", matchingRoute);
return matchPath(req.path, matchingRoute);
}
let routeData = getRouteData();
console.log("Route Data: ", routeData);
}
/** REST OF COMPONENT **/
Console.log output for the url www.example.com/news-feed/test would be
Route Data: { path: '/news-feed/:id',
url: '/news-feed/test',
isExact: true,
params: { id: 'test' } }
As you can see, we've found our param on the server-side using no regex. matchPath() did the work for us. We can use nice, clean urls.
Server-side index.js
Where the initial action is called, with the promise resolve, reject, and req objects. Keep in mind this is a firebase hosting example and may differ for different hosting providers - your method for the initialAction function call may also differ.
import React from "react";
import ReactDOMServer from 'react-dom/server';
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { StaticRouter, matchPath } from "react-router-dom";
import routes from "../shared/components/App/routes.js";
import express from "express";
import * as functions from "firebase-functions";
// Import Components, Reducers, Styles
import App from "../shared/components/App";
import reducers from "../shared/reducers";
// Prepare our store to be enhanced with middleware
const middleware = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
// Create store, compatible with REDUX_DEVTOOLS (chrome extension)
const store = createStoreWithMiddleware(reducers);
// Implement cors middleware to allow cross-origin
const cors = require('cors')({ origin: true });
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Finds the component for the given route, runs the "initial action" on the component
// The initialAction is a function on all server-side renderable components that must retrieve data before sending the http response
// Initial action always requires (resolve, reject, req), and returns a promise.
const promises = routes.reduce((acc, route) => {
if (matchPath(req.url, route) && route.component && route.component.initialAction) {
acc.push(new Promise(function (resolve, reject) {
// console.log("Calling initial action...");
store.dispatch(route.component.initialAction(resolve, reject, req));
}));
}
return acc;
}, []);
// Send our response only once all promises (from all components included in the route) have resolved
Promise.all(promises)
.then(() => {
const context = {};
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
);
const preloadedState = store.getState();
res.status(200).send(renderFullPage(html, preloadedState));
})
.catch(function (error) {
console.log("Promise error at server", error);
});
});
});
module.exports = functions.https.onRequest(app);
Just used a sample node.js app to make a server.js which could be like
const express = require('express')
const app = express()
app.get('/search/:userid', (req, res) => res.json({ key: `Hello World for search with id=${req.params.userid}` }))
app.get('/search', (req, res) => res.send('Hello World!i for search'))
app.get('*', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
For the page number and other url params you can do like
req.query['page']
to retrieve the parameters.

Defining a ExpressJS route from a Typescript class

I'm trying to load ExpressJS routes from a Typescript class. The idea is that this should eventually be dynamic routes. But I'm already stuck at defininghard coded routes in a class.
My index.ts js looks like this:
import generic = require('./generic');
import Generic = generic.Generic;
class Test {
private app:any;
private port:number = 3000;
constructor() {
this.app = express();
new Generic();
this.app.listen(this.port, () => {
console.log('Listening on port ' + this.port);
});
}
}
export = Test;
new Test();
Then my generic.ts looks like this. I'm trying to define another route from this class:
module Generic {
export class Generic {
constructor() {
console.log('In generic');
var router:any = express.Router();
router.get('/', function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.status(200).send("AAA");
});
}
}
}
export = Generic;
When I run my application then I see the message In generic appear in console. But when I load my browser it says:
Cannot GET /
So for some reason it's not really registering the route from the Generic class. What am I doing wrong?
Take a look at the sample on Microsoft's github page. Rather than using express.Router, they simply use get and import the function from an external module. It has worked pretty well for me.
app.ts
import * as express from "express";
import * as routes from "./routes/index";
var app = express();
app.get('/', routes.index);
routes/index.ts
import express = require("express")
export function index(req: express.Request, res: express.Response) {
res.render('index', { title: 'ImageBoard'});
};

Resources