Use Jest to mock external user module in an imported module - javascript

I don't know if I'm missing something in the docs, but I have this situation:
// test.js
import User from './user'
it("should load initial data", async() => {
const users = new User()
const user = await users.load()
})
// User.js
import Api from './api'
export default class User {
async load() {
const res = await Api.fetch() // prevent/mock this in testing
}
}
What is the Jest-way to prevent/mock the external Api module in User.js. I do not want User.js to make a real network request within the test.
Further to this, I'm looking for a more generic mocking solution, ie. say I'm testing in React Native, and I want to mock NativeModules.SettingsManager.settings.AppleLocale, for example. Lets say Api.fetch() calls the line above, and doesn't make a HTTP request

spyOn in combination with mock functions like mockImplementation will provide what you are looking for.
Here is a working example:
// ---- api.js ----
export const getData = () => {
return Promise.resolve('hi');
}
// ---- user.js ----
import { getData } from './api'
export default class User {
async load() {
return await getData(); // mock this call in user.test.js
}
}
// ---- user.test.js ----
import User from './user'
import * as Api from './api'; // import * so we can mock 'getData' on Api object
describe('User', () => {
it('should load initial data', async() => {
const mock = jest.spyOn(Api, 'getData'); // create a spy
mock.mockImplementation(() => Promise.resolve('hello')); // give it a mock implementation
const user = new User();
const result = await user.load();
expect(result).toBe('hello'); // SUCCESS, mock implementation called
mock.mockRestore(); // restore original implementation when we are done
});
});

If you need to mock responses to HTTP requests, then you should check out nock. It has a clean API that allows a lot of flexibility in creating HTTP responses to specific requests.

Related

Jest mock axios does not work if the class is initialised in the setupTests

I'm trying to test my backend class which uses axios to get some data from the backend. I've mocked axios and stubbed the get function to return some values. This works great!
However if I create a new instance of the Backend in the setupTests the test fails. If I remove the beforeEach from the setupTests and add it to the test file, the test succeeds again.
The test fails because the stub has not been called.
I'm using the setupTests to mock the dependencies for other tests. Whilst mocking this, I create a subclass instance of the Backend.
Test:
import {Backend} from "../../../shared/utils/backend";
import axios from "axios";
jest.mock("axios");
describe("backend", () => {
test("When getList is called data is returned", async () => {
(axios.get as jest.Mock) = jest.fn().mockResolvedValue(Promise.resolve({
data: [{
prop: "test",
otherProp: 2
}]
}));
const backend = new Backend("baseUrl");
const data = await backend.getList("testUrl");
expect(axios.get).toHaveBeenCalledWith("baseUrl/testUrl");
expect(data).toHaveLength(1);
});
});
SetupTests
import "#testing-library/jest-dom";
import {Backend} from "./shared/utils/backend";
new Backend("test");
Backend class
import axios from "axios";
export class Backend {
private _baseUrl: string;
constructor(baseUrl: string) {
axios.defaults.headers.get["Access-Control-Allow-Origin"] = "*";
axios.defaults.headers.get["Access-Control-Allow-Credentials"] = "true";
this._baseUrl = baseUrl;
}
async getList(url: string): Promise<any[] | undefined> {
const data = await axios.get<any[]>(`${this._baseUrl}/${url}`);
if (data) {
return data.data;
}
return undefined;
}
}
In the end I managed to circumvent the problem. My dependencies wanted a Backend in the constructor. My BackendMock was an extension of the Backend class. This resulted in implicitly creating a Backend whenever I created the mock.
I created an interface for the Backend and changed the dependencies to use the interface instead of the class. Now the Backend is no longer created and the tests now work.

Nestjs Interceptor not being invoked

I am using NestJS with a serverless app (deployed to AWS Lambda). I now have a need to use middleware, or Interceptors as they are called in nest, but I'm struggling to get them to work. I have changed from using NestFactory.createApplicationContext to NestFactory.create, as per the docs, that's what wraps Controller methods with enhancers, e.g. Interceptors
I am registering the Interceptor in a module, so it should be globally available
const loggingInterceptorProvider = {
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
};
My bootstrap looks like so
export async function bootstrap(Module: any) {
if (app) return app;
app = await NestFactory.createApplicationContext(Module);
return await app.init();
}
Now the non-standard bit, because I am using a generic "builder" (library code), the builder is passed the controller name as a string, and it is then invoked, as such
// the Module is accessible in the bootstrap via a closure, not shown in this code
const app = await bootstrap();
const appController = app.get(Controller);
// functionName is a string
const controllerFunction = appController[functionName];
const boundControllerFunction = controllerFunction.bind(
appController,
);
const result = await boundControllerFunction(body);
I am not seeing any of my Interceptor logging output. Am I doing something wrong? Or is it the way I am invoking the Controller that is not working with Interceptors?
EDIT:
For completeness, this is the correct bootstrap function I use
let cachedApp: INestApplication;
export async function bootstrap(Module: any) {
if (cachedApp) return cachedApp;
cachedApp = await NestFactory.create(Module, {
bufferLogs: true,
logger: ['error', 'warn'],
});
await cachedApp.init();
return cachedApp;
}
It happens because you've called the controller method directly, bypassing the nestjs lifecycle. When nest js server handles the request it applies its internal mechanisms for running interceptors, validation pipes, and exception filters. If you call class method directly it will not be used.
In your case you can follow this section of nestjs documentation:
https://docs.nestjs.com/faq/serverless#example-integration
let server: Handler;
async function bootstrap(): Promise<Handler> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
server = server ?? (await bootstrap());
return server(event, context, callback);
};
The "standalone application feature" from docs is useful if you want to call some service code, not a controller.
By the way, in the code snippet, you can see the variable server, they moved it outside of a handler function intentionally. Because in AWS lambdas it can be cached between different requests.
I found a/the way to do it, using the very poorly documented feature ExternalContextCreator. So basically the last code snippet I posted above, would become this
import { ExternalContextCreator } from '#nestjs/core/helpers/external-context-creator';
// the Module is accessible in the bootstrap via a closure, not shown in this code
const app = await bootstrap();
const appController = app.get(Controller);
// functionName is a string
const controllerFunction = appController[functionName];
const extContextCreator = app.get(ExternalContextCreator);
const boundControllerFunction = extContextCreator.create(
appController,
controllerFunction,
String(functionName),
);
const result = await boundControllerFunction(body);

How to mock module which depends implicitly per test in Jest?

I have an integration test where I make actual DB calls to the MongoDB database. But in order to test whether the transaction is expired or not, I need to mock the DB for that particular test. There are many reasons for me to make the actual DB call, I'm mentioning the state just for the sake of this example.
Jest has jest.doMock function but that is helpful only when I wanted to import the function within the test but in my case, It's the DB function which I wanted to mock for that particular test when is getting called inside the express middleware.
There is another option to mock the entire ../db module but that will complicate the tests a lot in my actual project. It would be very easy for me if I can mock the DB call for a specific test and for rest all the tests it should make the real DB calls.
Is there a way to do it in Jest?
// a.ts
import express from "express"
import db from "../db";
const app = express()
app.get("/api/deduct-balance/:txn_id", (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
});
// a.test.ts
import db from "../db";
describe("mixed tests", () => {
test("should make REAL db calls", async () => {
await axios.get("/api/deduct-balance/123")
const txn = await db.findById("123");
expect(txn.state).toBe("DEDUCTED");
});
test("should use MOCKED value", async () => {
// need a way to mock the DB call so that I can return an expired transaction
// when I hit the API
const { data } = await axios.get("/api/deduct-balance/123")
expect(data).toBe({
error: {
message: "txn expired"
}
});
});
})
Integration tests are overkill for this scenario. Simple unit tests would suffice. They are fast to execute, test exactly one thing and you should have lots of them.
Because you're defining the handler as an anonymous function it's hard to unit test by default. So the first action is to make it easier to test by extracting it.
// deduct-balance-handlers.ts
export const deductBalanceByTransaction = async (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
}
It will also makes the app configuration more clean.
// a.ts
import express from "express"
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
const app = express()
app.get("/api/deduct-balance/:txn_id", deductBalanceByTransaction);
Now it's easy to reuse the handler in your test without relying on the web framework or database.
// a.test.ts
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
jest.mock('../db');
describe("deduct-balance", () => {
test("Expired transaction should respond with 401 status", async () => {
const response = mockResponse();
deductBalanceByTransaction(request, response);
expect(response.status).toBe(401);
});
})
For simplicity's sake I left the part of creating a mock response and mocking the module out of the code. More can be learned about mocking here: https://jestjs.io/docs/en/manual-mocks

Sinon Spy never called but it should be

Problem
I'm testing a custom redux middleware using Jest and SinonJS and more precisely I want to test if some functions are called on special conditions inside the middleware.
I use SinonJS for creating the spies and I run my tests with Jest. I initialised the spies for the specific functions I want to track and when I check if the spies has been called, the spies has not been even if it should be (manually tested).
Code
Here is the middleware I want to test :
import { Cookies } from 'react-cookie';
import setAuthorizationToken from './setAuthorizationToken';
let cookies = new Cookies();
export const bindTokenWithApp = (store) => (next) => (action) => {
// Select the token before action
const previousToken = getToken(store.getState());
// Dispatch action
const result = next(action);
// Select the token after dispatched action
const nextToken = getToken(store.getState());
if (previousToken !== nextToken) {
if (nextToken === '') {
setAuthorizationToken(false);
cookies.remove(SESSION_COOKIE_NAME, COOKIE_OPTIONS);
} else {
cookies.set(SESSION_COOKIE_NAME, nextToken, COOKIE_OPTIONS);
setAuthorizationToken(nextToken);
}
}
return result;
};
Here is my actual test
import { bindTokenWithApp } from './middleware';
import { Cookies } from 'react-cookie';
import sinon, { assert } from 'sinon';
import setAuthorizationToken from './setAuthorizationToken';
describe('bindTokenWithApp', () => {
const next = jest.fn();
const action = jest.fn();
let cookies = new Cookies();
it('removes cookies when there is no token', () => {
// My actual not working spies
const cookieSpy = sinon.spy(cookies.remove);
const authSpy = sinon.spy(setAuthorizationToken);
// Stub for the specific case. This code works,
// I console.logged in the middleware and I'm getting the below values
const getState = sinon.stub();
getState.onFirstCall().returns({ auth: { token: 'a token' } });
getState.onSecondCall().returns({ auth: { token: '' } });
const store = { getState: getState };
bindTokenWithApp(store)(next)(action);
assert.calledOnce(cookieSpy);
assert.calledOnce(authSpy);
// Output : AssertError: expected remove to be called once but was called 0 times
// AssertError: expected setAuthorizationToken to be called once but was called 0 times
cookieSpy.restore(); // <= This one works
authSpy.restore(); // TypeError: authSpy.restore is not a function
});
});
I've read SinonJS doc and a few StackOverFlow posts but without solutions. I also can't call authSpy.restore();. I think I do not initialise spies the right way and I'm misunderstanding a concept in SinonJS but I can't find which one !
The setAuthorizationToken signature is
(alias) const setAuthorizationToken: (token: any) => void
import setAuthorizationToken
I think it's a classical module so I can't figure out why I struggle with authSpy.restore();
The two spies you have actually have two different fixes, both with the same underlying problem. sinon.spy(someFunction) doesn't actually wrap someFunction itself, it returns a spy for it but doesn't perform any replacement.
For the first spy, there exists a shorthand to automatically wrap an object method: sinon.spy(cookie, 'remove') should do what you need.
For the second spy, it is more complicated as you need to wrap the spy around the default export of setAuthorizationToken. For that you will need something like proxyquire. Proxyquire is a specialized require mechanism that allows you to replace imports with your desired test methods. Here's a brief of what you'll need to do:
const authSpy = sinon.spy(setAuthorizationToken);
bindTokenWithApp = proxyquire('./middleware', { './setAuthorizationToken': authSpy});

Dependency Injection in a redux action creator

I'm currently building a learner React/Redux Application and I can not wrap my head around how to do dependency injection for services.
To be more specific: I have a BluetoothService (which abstracts a 3rd Party Library) to scan for and connect to other devices via bluetooth. This service gets utilized by the action creators, something like this:
deviceActionCreators.js:
const bluetoothService = require('./blueToothService')
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
module.exports = { addDevice, startDeviceScan };
(I am using the thunk-middleware)
My Problem however is: how to inject the service itself into the action-creator?
I don't want that hard-coded require (or importin ES6) as I don't think this is a good pattern - besides making testing so much harder. I also want to be able to use a mock-service while testing the app on my work station (which doesn't have bluetooth) - so depending on the environment i want another service with the same interface injected inside my action-creator. This is simply not possible with using a static import.
I already tried making the bluetoothService a parameter for the Method itself (startDeviceScan(bluetoothService){}) - effectively making the method itself pure - but that just moves the problem to the containers using the action. Every container would have to know about the service then and get an implementation of it injected (for example via props).
Plus when I want to use the action from within another action I end up with the same problem again.
The Goal:
I want to decide on bootstrapping time which implemenation to use in my app.
Is there a good way or best practice for doing this?
React-thunk supports passing an arbitrary object to a thunk using withExtraArgument. You can use this to dependency-inject a service object, e.g.:
const bluetoothService = require('./blueToothService');
const services = {
bluetoothService: bluetoothService
};
let store = createStore(reducers, {},
applyMiddleware(thunk.withExtraArgument(services))
);
Then the services are available to your thunk as a third argument:
function startDeviceScan() {
return function (dispatch, getstate, services) {
// ...
services.bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
This is not as formal as using a dependency-injection decorator in Angular2 or creating a separate Redux middleware layer to pass services to thunks---it's just an "anything object" which is kind of ugly---but on the other hand it's fairly simple to implement.
You can use a redux middleware that will respond to an async action. In this way you can inject whatever service or mock you need in a single place, and the app will be free of any api implementation details:
// bluetoothAPI Middleware
import bluetoothService from 'bluetoothService';
export const DEVICE_SCAN = Symbol('DEVICE_SCAN'); // the symbol marks an action as belonging to this api
// actions creation helper for the middleware
const createAction = (type, payload) => ({
type,
payload
});
// This is the export that will be used in the applyMiddleware method
export default store => next => action => {
const blueToothAPI = action[DEVICE_SCAN];
if(blueToothAPI === undefined) {
return next(action);
}
const [ scanDeviceRequest, scanDeviceSuccess, scanDeviceFailure ] = blueToothAPI.actionTypes;
next(createAction(scanDeviceRequest)); // optional - use for waiting indication, such as spinner
return new Promise((resolve, reject) => // instead of promise you can do next(createAction(scanDeviceSuccess, device) in the success callback of the original method
bluetoothService.startDeviceSearch((device) => resolve(device), (error) = reject(error)) // I assume that you have a fail callback as well
.then((device) => next(createAction(scanDeviceSuccess, device))) // on success action dispatch
.catch((error) => next(createAction(scanDeviceFailure, error ))); // on error action dispatch
};
// Async Action Creator
export const startDeviceScan = (actionTypes) => ({
[DEVICE_SCAN]: {
actionTypes
}
});
// ACTION_TYPES
export const SCAN_DEVICE_REQUEST = 'SCAN_DEVICE_REQUEST';
export const SCAN_DEVICE_SUCCESS = 'SCAN_DEVICE_SUCCESS';
export const SCAN_DEVICE_FAILURE = 'SCAN_DEVICE_FAILURE';
// Action Creators - the actions will be created by the middleware, so no need for regular action creators
// Applying the bluetoothAPI middleware to the store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import bluetoothAPI from './bluetoothAPI';
const store = createStore(
reducers,
applyMiddleware(bluetoothAPI);
);
// Usage
import { SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE } from 'ACTION_TYPES';
dispatch(startDeviceScan([SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE]));
You dispatch the startDeviceScan async action, with the action types that will be used in the creation of the relevant actions. The middleware identifies the action by the symbol DEVICE_SCAN. If the action doesn't contain the symbol, it dispatches it back to the store (next middleware / reducers).
If the symbol DEVICE_SCAN exists, the middleware extracts the action types, creates and dispatches a start action (for a loading spinner for example), makes the async request, and then creates and dispatches a success or failure action.
Also look at the real world redux middle example.
Can you wrap your action creators into their own service?
export function actionCreatorsService(bluetoothService) {
function addDevice(device) {
return { type: 'ADD_DEVICE', device }
}
function startDeviceScan() {
return function (dispatch) {
// The Service invokes the given callback for each found device
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
}
}
return {
addDevice,
startDeviceScan
};
}
Now, any clients of this service will need to provide an instance of the bluetoothService. In your actual src code:
const bluetoothService = require('./actual/bluetooth/service');
const actionCreators = require('./actionCreators')(bluetoothService);
And in your tests:
const mockBluetoothService = require('./mock/bluetooth/service');
const actionCreators = require('./actionCreators')(mockBluetoothService);
If you don't want to specify the bluetooth service every time you need to import the action creators, within the action creators module you can have a normal export (that uses the actual bluetooth service) and a mock export (that uses a mock service). Then the calling code might look like this:
const actionCreators = require('./actionCreators').actionCreators;
And your test code might look like this:
const actionCreators = require('./actionCreators').mockActionCreators;
I created a dependency-injecting middleware called redux-bubble-di for exactly that purpose. It can be used to inject an arbitrary number of dependencies into action creators.
You can install it by npm install --save redux-bubble-di or download it.
Your example using redux-bubble-di would look like this:
//import { DiContainer } from "bubble-di";
const { DiContainer } = require("bubble-di");
//import { createStore, applyMiddleware } from "redux";
const { createStore, applyMiddleware } = require("redux");
//import reduxBubbleDi from "redux-bubble-di";
const reduxBubbleDi = require("redux-bubble-di").default;
const bluetoothService = require('./blueToothService');
DiContainer.setContainer(new DiContainer());
DiContainer.getContainer().registerInstance("bluetoothService", bluetoothService);
const store = createStore(
state => state,
undefined,
applyMiddleware(reduxBubbleDi(DiContainer.getContainer())),
);
const startDeviceScan = {
bubble: (dispatch, bluetoothService) => {
bluetoothService.startDeviceSearch((device) => {
dispatch(addDevice(device));
});
},
dependencies: ["bluetoothService"],
};
// ...
store.dispatch(startDeviceScan);

Categories

Resources