I would like to know the best way to test multiple routes in expressJS with jest.
My current problem is when I try testing multiple routes that uses same mocked function, the last route throws an undefined error.
Here's the context in code.
app.ts
import * as express from "express";
import controllerOne from "./controllers/one";
import controllerTwo from "./controllers/two";
const app = express();
app.use("/one", controllerOne);
app.use("/two", controllerTwo);
export default app;
controllers/one.ts
import getDbConnection from "../db";
import * as express from "express";
const router = express.Router();
const db = getDbConnection();
router.get("/", async (_, res) => {
const sql = "SELECT * from table";
const result = await db.query(sql);
res.status(200).json(result);
});
export default router;
controllers/two.ts
import getDbConnection from "../db";
import * as express from "express";
const router = express.Router();
const db = getDbConnection();
router.get("/", async (_, res) => {
const sql = "SELECT * from table";
const result = await db.query(sql);
res.status(200).json(result);
});
export default router;
tests/one.ts
import * as request from "supertest";
import { mocked } from "ts-jest/utils";
import db from "../db";
jest.mock("../db");
const mockedDB = mocked(db);
const data = ["data"];
describe("Controller One", () => {
afterAll(() => {
jest.restoreAllMocks();
jest.resetModules();
});
beforeEach(() => {
const mockedQueryFn = {
query: jest.fn().mockResolvedValueOnce(data),
};
mockedDB.mockReturnValueOnce(mockedQueryFn);
});
it("should retrieve one", async () => {
const mod = require("../src/app");
const app = (mod as any).default;
await request(app)
.get("/one")
.expect(200)
.expect(function (res) {
expect(res.body).toEqual(data);
});
});
});
tests/two.ts
import * as request from "supertest";
import { mocked } from "ts-jest/utils";
import db from "../db";
jest.mock("../db");
const mockedDB = mocked(db);
const data = ["data"];
describe("Controller One", () => {
afterAll(() => {
jest.restoreAllMocks();
jest.resetModules();
});
beforeEach(() => {
const mockedQueryFn = {
query: jest.fn().mockResolvedValueOnce(data),
};
mockedDB.mockReturnValueOnce(mockedQueryFn);
});
it("should retrieve one", async () => {
const mod = require("../src/app");
const app = (mod as any).default;
await request(app)
.get("/two")
.expect(200)
.expect(function (res) {
expect(res.body).toEqual(data);
});
});
});
Now my problem is if I run the test, test/one.ts would pass test/two.ts would fail with the error TypeError: Cannot read property 'query' of undefined referencing this line const result = await db.query(sql)
When I switch up the controller imports
import controllerTwo from "./controllers/two";
import controllerOne from "./controllers/one";
then test/two.ts passes and test/one.ts fails with same error.
I would like to also mention that with/without reseting jest mock, I get same result.
It may have to do with the fact that you’re not calling done at the end of your test. Inside the expect part of the test, call done(). You also need to change the callback so that it like this:
it("should retrieve one", async (done) => {
Related
I have an endpoint like this:
Router.ts
router.get('/endpoint', async (req: Request, res: Response, next: NextFunction) => {
const myId = req.params.myId;
const data = await getData(myId);
});
controller.ts
export async function getData(myId: string): Promise<ICollectionDocument> {
return myModel.findById(myId).lean(); // pay attention to .lean()
}
It is working as expected, but when I try to write a test for this, I get:
.findById(...).lean is not a function",
Test
import * as request from 'supertest';
import * as sinon from 'sinon';
import * as chai from 'chai';
import * as express from 'express';
import * as applicationSetup from '../src/app'
import * as db from '../src/db';
import { User } from ''../src/models/user;
describe('Request Handler', () => {
let UserStub: sinon.stub;
let app: express.Application;
before(async () => {
UserStub = sinon.stub(User, 'findOne');
app = await applicationSetup.init();
await db.init();
});
it('Should do something', async() => {
// Also tried with and without this piece of code
UserStub.returns({
exec: sinon.stub().resolves(),
});
const resp = await request(app)
.get('/endpoint')
.send()
resp.status.should.be.equal(200);
})
});
please can someone help me to get the data of products from the backend/data by using Axios.get method to retrieve with the helping of redux store.I do all the steps to get the contain of products but the console send me the initial state empty object so there are all my files :
productActions.js :
import Axios from 'axios';
const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST";
const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_REQUEST";
const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL";
export const listProducts =
() => async (dispatch) => {
dispatch({
type: PRODUCT_LIST_REQUEST
});
try {
const {data} = await Axios.get('/api/products');
dispatch({type: PRODUCT_LIST_SUCCESS, payload: data});
} catch(error) {
dispatch({type: PRODUCT_LIST_FAIL, payload: error.message});
}
}
ProductReducers.js:
const {PRODUCT_LIST_REQUEST} = require('../actions/productActions.js');
const {PRODUCT_LIST_SUCCESS} = require('../actions/productActions.js');
const {PRODUCT_LIST_FAIL} = require('../actions/productActions.js')
export const productListReducer = (state = {loading: true, products: {}}, action) => {
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return {loading: true};
case PRODUCT_LIST_SUCCESS:
return ({loading: false, products: action.payload});
case PRODUCT_LIST_FAIL:
return ({loading: false, error: action.payload});
default:
return state;
}
};
this store.js:
import {createStore, compose, applyMiddleware, combineReducers} from 'redux';
import thunk from 'redux-thunk';
import {productListReducer} from './reducers/productReducers';
const initialState = {};
const reducer = combineReducers({
productList: productListReducer
})
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, initialState, composeEnhancer(applyMiddleware(thunk)));
export default store;
server.js :
import express from 'express';
import {data} from './data.js';
const app = express();
app.get('/api/products', (req, res) => {
res.send(data.categories)
});
app.get('/', (req, res) => {
res.send('server is ready')
});
const port = process.env.port || 5001;
app.listen(port, ()=> {
console.log(`server is runing at http://localhost:${port}`);
})
I am not sure but, I think you must return in your switch cases like this:
return {...state, loading: false, products: action.payload};
Just try all of them like this. And try to console logging the data you get from axios.get. I am pretty sure that you must use JSON.parse(data) before setting it to your payload.
W.r.t. How to mock firestore with mocha how do I mock the following firestore query using sinon?
import * as admin from "firebase-admin";
const db: FirebaseFirestore.Firestore = admin.firestore();
const storeSnapshot: = await db
.doc(`/Client/${clientId}/Store/${storeId}`)
.get();
I tried:
import * as _sinon from 'sinon';
it('Query Client collection not empty test', async () => {
const clientStoreDocStub = _sinon.stub(db, "doc");
clientStoreDocStub.withArgs("/Client/123/Store/789").resolves({
id: "789",
settings: [ {setting1: "Setting1-value", setting2: "Setting2-value"}, {setting1: "Setting3-value", setting2: "Setting4-value"}]
});
clientStoreDocStub.withArgs("/Client/456/Store/012").resolves({
id: "012",
settings: [ {setting1: "Setting5-value", setting2: "Setting6-value"}, {setting1: "Setting7-value", setting2: "Setting8-value"}]
});
const storeSnapshot: FirebaseFirestore.DocumentSnapshot = await db
.doc("/Client/123/Store/789")
.get();
const store = storeSnapshot.data();
});
but get the following error:
1) Mock firebase firestore Tests
Query Client collection not empty test:
TypeError: dbInit_1.db.doc(...).get is not a function
import * as _chai from "chai";
import * as chaiAsPromised from "chai-as-promised";
import * as admin from "firebase-admin";
import * as _sinon from 'sinon';
_chai.use(chaiAsPromised);
var expect = _chai.expect;
const db: FirebaseFirestore.Firestore = admin.firestore();
describe("Test with mock firestore", () => {
var docRefStub;
beforeEach(() => { docRefStub = _sinon.stub(db, "doc");});
afterEach(() => {db.doc.restore();});
it("Test should pass with valid request params", async () => {
const expectedString = "Hello World!!!";
const testCollection = {
"/Client/123/Store/789": {
data: 1,
moreData: "Hello World!!!"
}
}
const setSpy = _sinon.spy();
docRefStub.callsFake(fakeFsDoc(testCollection, setSpy));
await callAFunction("123", "789");
expect(setSpy.called).to.be.true;
expect(setSpy.getCall(0).args[0].data).to.not.be.null;
expect(setSpy.getCall(0).args[0].moreData).to.deep.equal(expectedString);
}
});
export function fakeFsDoc(database, setSpy = null) {
return docId => {
const data = database[docId] ?? undefined;
return {
get: async () => ({
data: () => data
}),
set: setSpy
}
}
}
I have an expressjs backend that has an axios call to unbounce that will send the value to the front end, The issue is the value changes over time so I am trying to catch the new value every minute. Right now it will send the initial value but it will not give the updated value without restarting the expressjs backend. Any idea on how to update the state in the front end to show the correct value once per minute.
Front end:
import "./App.css";
import axios from "./axios";
import { ProgressBar } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
const [result, setResult] = useState("");
function getInfo() {
axios.get("api/hello").then((response) => {
console.log("changed")
setResult(response.data);
});
}
useEffect(() => {
getInfo()
}, [setResult]);
const total = result/1000000;
const percent = total * 100;
return (
<main>
<ProgressBar now={percent} label={`${Math.round(percent)}%`} />
<div className="test">Some text that shows the result at the end {result}</div>
</main>
);
}
export default App;
Express Backend:
const axios = require("axios");
const cors = require("cors");
const cron = require("node-cron");
const PORT = process.env.PORT || 5000;
const server = express();
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
server.use(cors());
const getCount = async () => {
const config = {
auth: {
username: 'APIKEY',
},
headers: {
'accept': 'application/vnd.unbounce.api.v0.4+json'
}
};
try {
return await axios.get('https://api.unbounce.com/pages/PAGE_ID', config)
} catch (error) {
console.error(error.response.data)
}
}
cron.schedule('* * * * *', function() {
console.log('getCount Ran');
getCount();
});
const countCount = async () => {
const count = await getCount()
const isCount = count.data.tests.current.conversions;
if (count) {
console.log(isCount)
}
return server.get("/api/hello", async function (req, res) {
res.json(isCount);
});
}
cron.schedule('* * * * *', function() {
console.log('countCount Ran');
countCount();
});
server.listen(PORT, () => console.log(`listening on port ${PORT}`));
The console.log will show the correct value every iteration but the front end just keeps the initial value with no change.
Hello in the redux documentation for testing they have have this example to test api calls:
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/counter'
import * as types from '../../constants/ActionTypes'
import nock from 'nock'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('async actions', () => {
afterEach(() => {
nock.cleanAll()
})
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { body: { todos: ['do something'] }})
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
})
I'm using karma test enviroment, and I think I can't use nock to test this. So I was looking into testing this using Sinon instead. Trouble is i don't understand how i would test using this as I'm not passing a callback into my api function call. I'm using axios to call my external API.
For this you should use axios-mock-adapter
Example:
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import * as actionTypes from './userConstants';
import * as actions from './userActions';
const mockAxios = new MockAdapter(axios);
const mockStore = configureMockStore(middlewares);
describe('fetchCurrentUser', () => {
afterEach(() => {
mockAxios.reset();
});
context('when request succeeds', () => {
it('dispatches FETCH_CURRENT_USER_SUCCESS', () => {
mockAxios.onGet('/api/v1/user/current').reply(200, {});
const expectedActions = [
{ type: actionTypes.SET_IS_FETCHING_CURRENT_USER },
{ type: actionTypes.FETCH_CURRENT_USER_SUCCESS, user: {} }
];
const store = mockStore({ users: Map() });
return store.dispatch(actions.fetchCurrentUser()).then(() =>
expect(store.getActions()).to.eql(expectedActions)
);
});
});
I'm not an expert on async actions since in my app i test all these things separately (action creator, api calls with nock mocking a service, asynchronous behavior thanks to saga, however in the redux docs code looks like this
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchTodos())
.then(() => { // return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
So the dispatch returns your async action, and you have to pass test in the function that will be executed when your async action resolves. Nock'ing an endpoint should work just fine.