I am having trouble in mocking ExecutionContext in Guard middleware.
Here's my RoleGuard extends JwtGuard
#Injectable()
export class RoleGuard extends JwtAuthGuard {
...
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const params = request.params;
...
}
}
This is what I am trying on my unit test.
let context: ExecutionContext = jest.genMockFromModule('#nestjs/common');
context.switchToHttp = jest.fn().mockResolvedValue({
getRequest: () => ({
originalUrl: '/',
method: 'GET',
params: undefined,
query: undefined,
body: undefined,
}),
getResponse: () => ({
statusCode: 200,
}),
});
jest.spyOn(context.switchToHttp(), 'getRequest').mockImplementation(() => {
return Promise.resolve(null);
});
And I am getting this kind of error.
Cannot spy the getRequest property because it is not a function; undefined given instead
I would like you to suggest any other way to mock context. Thank you.
Please check this library https://www.npmjs.com/package/#golevelup/ts-jest
Then you could mock ExecutionContext as following.
import { createMock } from '#golevelup/ts-jest';
import { ExecutionContext } from '#nestjs/common';
describe('Mocked Execution Context', () => {
it('should have a fully mocked Execution Context', () => {
const mockExecutionContext = createMock<ExecutionContext>();
expect(mockExecutionContext.switchToHttp()).toBeDefined();
...
});
});
Hope it helps
When it comes to the ExecutionContext, depending on what I'm tetsting, I just supply a simple object instead, something like
const ctxMock = {
switchToHttp: () => ({
getRequest: () => ({
params: paramsToAdd,
url: 'some url path',
...
}),
}),
}
And use that as I need. If I need access to jest functions I save those to a variable before hand and assign the context's function to the variable, then I can use expect(variable).toHaveBeenCalledTimes(x) without a problem.
Another option is to use #golevelup/ts-jest to create type safe mock objects for you. I've made extensive use of this library as well for other libraries I've made.
Related
I have a test class that tests behavior of various HTTP methods in a Nest controller class. I am using Jest manual mocks to stub the behavior of various functions in the service class so that I do not have to rely on actual dependencies/services, eg. snowflake. I have a top level jest.mock() defined as follows which initializes the mocked version of the service class instead of the actual service class.The mocked service class is created inside mocks folder adjacent to the actual service class.
I am redefining the behavior of one of the mocked functions in the 'error scenario' describe block as shown in the code snippet below, for testing the error scenario . The test scenario : 'throws an error' is failing as it is still picking up the default mocked behavior. Any pointers or help is appreciated.
In short, I want to be able to define different mocked behavior for a single function of the same mocked class for various test scenarios.
Thanks
jest.mock('#modules/shipment-summary/shipment-summary.service');
describe('ShipmentSummaryController', () => {
let shipmentSummaryController: ShipmentSummaryController;
let shipmentSummaryService: ShipmentSummaryService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [],
controllers: [ShipmentSummaryController],
providers: [ShipmentSummaryService],
}).compile();
shipmentSummaryController = moduleRef.get<ShipmentSummaryController>(
ShipmentSummaryController,
);
shipmentSummaryService = moduleRef.get<ShipmentSummaryService>(
ShipmentSummaryService,
);
jest.clearAllMocks();
});
//All the tests inside this describe block work as expected
describe('valid shipment-mode scenario', () => {
describe('valid shipment modes for tenant', () => {
let modes: ShipmentMode[];
beforeEach(async () => {
modes = await shipmentSummaryController.getAllShipmentModes('256');
});
test('calls the service fn. with the correct arg', () => {
expect(shipmentSummaryService.getAvailableShipmentModes).toBeCalledWith(
'256',
);
});
test('all available shipment modes for 256 are returned', () => {
expect(modes).toEqual(validModeDropdown());
});
});
});
// redefining behavior of getAllshipmentModes() is not working
describe('error scenario', () => {
let modes: ShipmentMode[] = []
beforeEach(async () => {
modes = await shipmentSummaryController.getAllShipmentModes('256');
});
beforeAll(() => {
jest.clearAllMocks();
jest.mock('#modules/shipment-summary/shipment-summary.service.ts', () => {
return {
getAvailableShipmentModes: () => {
throw new Error('Test error');
},
}
});
});
test('throws an error', () => {
expect(() => shipmentSummaryController.getAllShipmentModes('256')).toThrow();
})
})
});
My mocked service class is as follows:
export const ShipmentSummaryService = jest.fn().mockReturnValue({
// Fn. to be mocked differently per test scenario.
getAvailableShipmentModes: jest.fn().mockResolvedValue(validModeDropdown()),
});
There are many ways of accomplishing this. The Nest docs outline a number of them. However, one of my preferred ways, useValue, is not as clear as it could be, so I'll added it here.
This example will also use jest in order to spy on a mock, changing its behavior depending on the test.
Imagine these two simple resources
Injectable();
export class SimpleService {
public sayHello(): string {
return "Hello, world!";
}
}
#Controller()
export class SimpleController {
constructor(
#Inject(SimpleService) private readonly simpleService: SimpleService
) {}
#Get()
public controllerSaysHello(): string {
return this.simpleService.sayHello();
}
}
Your tests could look something like this
describe("SimpleController", () => {
let controller: SimpleController;
const mockReturnValue = "Goodbye, world..",
mockSimpleService: SimpleService = {
sayHello: () => mockReturnValue,
};
beforeEach(() => {
jest.restoreAllMocks();
});
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
SimpleController,
{ provide: SimpleService, useValue: mockSimpleService },
],
}).compile();
controller = module.get(SimpleController);
});
test("default mockSimpleService", () => {
const result = controller.controllerSaysHello();
expect(result).toBe(mockReturnValue);
});
test("spied on mockSimpleService", () => {
const differentReturnValue = "Hallo!";
jest
.spyOn(mockSimpleService, "sayHello")
.mockReturnValue(differentReturnValue);
const result = controller.controllerSaysHello();
expect(result).toBe(differentReturnValue);
});
});
My component calls
this.axios.get()
when being mounted and passes a vuex-store variable to the api. The api returns an array as the response and the component displays some of the returned data after exchanging a loading-element with the real content.
In my unit test I want to simulate the result of the axios-request, wait for the transition between the loading- and the content-element and then finally check the validity of the content. However, the test fails and outputs:
Cannot read property 'get' of undefined
and highlights the get on this.axios.
Here is what I'm expecting to work (based on this guide):
... some imports etc. ...
const mockAxios = { whatIExpectToGet };
jest.mock("axios", () => ({
get: jest.fn(() => mockAxios)
}));
it("description of the test", async () => {
const wrapper = mount(MyComponent);
... code continues ...
Of course I'm accesssing axios via this and not directly like they do in the guide. But, since I can't find any mention of anything related to that, I assume that's irrelevant?
I also tried to mock axios myself like so:
... imports etc. ...
const axios = {
get: Promise.resolve({ whatIExpectToGet })
};
it("description of the test", async () => {
const wrapper = mount(MyComponent, {
global: {
mocks: [ axios ]
}
});
... code continues ...
Apparently people with similar problems used localVue.use() to inject stuff, but that's no longer supported.
Could someone be so kind and smart as to point me into the right direction, please?
Thank you.
-------------------> SOLUTION <-------------------
Thanks to tony 19 this question is already solved.
I ended up using an async function to mock axios because Promise.resolve() wasn't working for me:
import { shallowMount, flushPromises } from "#vue/test-utils";
import MyComponent from "#/components/MyComponent.vue";
describe("MyComponent.vue", () => {
const axios = {
get: async () => ({
data: { expectedData }
})
};
it("test description", async () => {
const wrapper = shallowMount(MyComponent, {
global: {
mocks: {
axios: axios
}
}
} as any);
expect(wrapper.html()).toContain("some_string_i_display_while_loading");
await flushPromises();
expect(wrapper.html()).toContain("some_string_i_display_after_getting_the_response");
});
});
Using global.mocks to mock axios is the right approach, but your attempt incorrectly used an array when it should've been an object:
const wrapper = mount(MyComponent, {
global: {
// mocks: [ axios ] ❌
mocks: { axios } ✅
}
})
Note axios.get() resolves to an axios.Response object, which stores the response data in its data property, so your mock should do the same.
Here's a full example:
// MyComponent.vue
export default {
mounted() {
this.axios.get('foo').then(resp => this.foo = resp.data)
}
}
// MyComponent.spec.js
it('gets foo', () => {
const wrapper = mount(MyComponent, {
global: {
mocks: {
axios: {
get: Promise.resolve({ data: { foo: true }})
// OR use an async function, which internally returns a Promise
get: async () => ({ data: { foo: true }})
}
}
}
}
})
I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});
I created an axios instance ...
// api/index.js
const api = axios.create({
baseURL: '/api/',
timeout: 2500,
headers: { Accept: 'application/json' },
});
export default api;
And severals modules use it ..
// api/versions.js
import api from './api';
export function getVersions() {
return api.get('/versions');
}
I try to test like ..
// Test
import { getVersions } from './api/versions';
const versions= [{ id: 1, desc: 'v1' }, { id: 2, desc: 'v2' }];
mockAdapter.onGet('/versions').reply(200, versions);
getVersions.then((resp) => { // resp is UNDEFINED?
expect(resp.data).toEqual(versions);
done();
});
Why resp is undefined?
Two things to try here:
Maybe you already have this elsewhere in your code, but be sure to set up mockAdaptor:
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mockAdapter = new MockAdapter(axios);
I haven't found a way to get the mock adapter working when the function you are testing uses 'axios.create' to set up a new axios instance. Try something along the lines of this instead:
// api/index.js
const api = {
get(path) {
return axios.get('/api' + path)
.then((response) => {
return response.data;
});
}
}
export default api;
For anyone still struggling with this.
You need to make sure you iniatilise your MockAdapter outside a test body.
ie.
❌ Incorrect ❌
it('should do a thing', () => {
const mockAdapter = new MockAdapter(axios);
})
✅ Correct ✅
const mockAdapter = new MockAdapter(axios);
it('should pass' () => {})
according to James M. advice, I updated my api/index.js , not using the axios.create...
api/index.js
import http from 'axios'
export default {
fetchShoppingLists: () => {
console.log('API FETCH SHOPPINGLISTS')
return http
.get('http://localhost:3000/shoppinglists')
.then(response => {
return response
})
.catch(error => {
console.log('FETCH ERROR: ', error)
})
}
}
You don't need axios-mock-adapter. Here is how I mock my axios:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios
I am new to redux testing and have been trying to back fill test for an application I made so sorry if this is the complete wrong way to go about testing with nock and redux-mock-store.
//Action in authAction.js
export function fetchMessage() {
return function(dispatch) {
axios.get(ROOT_URL, {
headers: { authorization: localStorage.getItem('token') }
})
.then(response => {
console.log("hi")
dispatch({
type: FETCH_MESSAGE,
payload: response.data.message
});
})
.catch(response => {
console.log(response)
//callingRefresh(response,"/feature",dispatch);
});
}
}
This is the method and it seems to be getting called but normally goes to the catch cause of nock failing cause of the header not matching.
//authActions_test.js
import nock from 'nock'
import React from 'react'
import {expect} from 'chai'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
import * as actions from '../../src/actions/authActions';
const ROOT_URL = 'http://localhost:3090';
describe('actions', () => {
beforeEach(() => {
nock.disableNetConnect();
localStorage.setItem("token", '12345');
});
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});
describe('feature', () => {
it('has the correct type', () => {
var scope = nock(ROOT_URL).get('/',{reqheaders: {'authorization': '12345'}}).reply(200,{ message: 'Super secret code is ABC123' });
const store = mockStore({ message: '' });
store.dispatch(actions.fetchMessage()).then(() => {
const actions = store.getStore()
expect(actions.message).toEqual('Super secret code is ABC123');
})
});
});
});
Even when the header is removed and the nock intercepts the call. I am getting this error every time
TypeError: Cannot read property 'then' of undefined
at Context.<anonymous> (test/actions/authActions_test.js:43:24)
You're not returning the promise from axios to chain the then call onto.
Change the thunk to be like:
//Action in authAction.js
export function fetchMessage() {
return function(dispatch) {
return axios.get(ROOT_URL, {
headers: { authorization: localStorage.getItem('token') }
})
.then(response => {
console.log("hi")
dispatch({
type: FETCH_MESSAGE,
payload: response.data.message
});
})
.catch(response => {
console.log(response)
//callingRefresh(response,"/feature",dispatch);
});
}
}
You may also need to change the test so that it doesn't pass before the promise resolves. How to do this changes depending on which testing library you use. If you're using mocha, take a look at this answer.
Side note: I'm not sure if you have other unit tests testing the action creator separately to the reducer, but this is a very integrated way to test these. One of the big advantages of Redux is how easily each seperate cog of the machine can be tested in isolation to each other.