I'm trying to test a service in Nestjs which is responsible for getting one record out of a mongo database, using Jest.
As per common convention, when writing unit tests that test services we can mock a record that would sit in a database.
I'm trying the following implementation:
import { Test } from '#nestjs/testing';
import { QuestionsService } from './questions.service';
import { CreateQuestionRequestDto } from './dto/create-question-request.dto';
import { getModelToken } from '#nestjs/mongoose';
import { UpdateQuestionRequestDto } from './dto/update-question-request.dto';
import { NotFoundException } from '#nestjs/common';
import { DuplicateQuestionRequestDto } from './dto/duplicate-question-request.dto';
const testQuestion: CreateQuestionRequestDto = {
data: {
createdBy: { id: 0, name: '' },
lanugageTexts: undefined,
options: undefined,
status: undefined,
type: undefined,
entityId: 1,
propertyId: 'propHash1',
companyId: 1,
entityType: 'announcement',
},
};
describe('QuestionsService', () => {
let questionService: QuestionsService;
let findOne: jest.Mock;
let findOneAndUpdate: jest.Mock;
let find: jest.Mock;
beforeEach(async () => {
// save = jest.fn();
findOne = jest.fn();
findOneAndUpdate = jest.fn();
find = jest.fn();
const module = await Test.createTestingModule({
providers: [
QuestionsService,
{
provide: getModelToken('Question'),
useValue: {}
}
]
})
.compile();
questionService = await module.get<QuestionsService>(QuestionsService);
});
it('should be defined', () => {
expect(questionService).toBeDefined();
});
/**
* Question Get
*/
describe('when getting a question', () => {
describe('and the questionId does not exist', () => {
beforeEach(() => {
findOne.mockReturnValue(undefined);
})
it('should throw a NotFound exception', async () => {
const response = await questionService.get('announcement', 9136500000);
expect(response).toThrow(NotFoundException);
});
});
describe('and the questionId exists', () => {
beforeEach(() => {
findOne.mockResolvedValue(Promise.resolve(testQuestion));
});
it('should update the correct question', async() => {
const response = await questionService.get('announcement', 1);
expect(response).toMatchObject(updatedTestQuestion);
});
});
});
});
When I run this test I get the following error message.
● QuestionsService › when getting a question › and the questionId does not exist › should throw a NotFound exception
TypeError: this.questionModel.find is not a function
52 | const data: Question[] = [];
53 | const questions = await this.questionModel
> 54 | .find(
| ^
55 | { entityType: entityType, entityId: entityId, status: QuestionStatus.ACTIVE },
56 | { answers: 0 },
57 | {
at QuestionsService.get (questions/questions.service.ts:54:14)
at Object.<anonymous> (questions/questions.spec.ts:128:56)
The service method I'm testing is.
async get(entityType: string, entityId: number): Promise<any> {
const data: Question[] = [];
const questions = await this.questionModel
.find(
{ entityType: entityType, entityId: entityId, status: QuestionStatus.ACTIVE },
{ answers: 0 },
{
sort: { _id: -1 },
limit: 1,
}
)
.exec();
if (!questions.length) {
throw new NotFoundException();
}
questions.forEach((question) => {
data.push(question);
});
return { data };
}
find() is the mongoose method that fetches the record from the database. I believe for the test I need to somehow include these methods I'm using in the service and mock them but I cannot find one clear answer.
Related
I'm trying to find a specific data based on the id in graphql.
But it is returning null .
I have also tried the mutation. Here is also it is returning null.
What's wrong with this below code.
const { ApolloServer } = require("#apollo/server");
const { startStandaloneServer } = require("#apollo/server/standalone");
const students = [
{
name: "langesh",
roll: 131,
},
{
name: "ram",
roll: 134,
},
];
const typeDefs = `#graphql
type Student {
name: String,
roll: Int,
}
type Query {
students: [Student]
student(roll: Int) : Student
}
`;
const resolvers = {
Query: {
students: () => students,
student: (parent, roll) => {
return students.find((s) => s.roll === roll);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
async function startServer() {
const { url } = await startStandaloneServer(server, {
listen: {
port: 8000,
},
});
console.log(`url : ${url}`);
}
startServer();
You need to destructure the args in your resolver.
Instead of:
student: (parent, roll) => {
return students.find((s) => s.roll === roll);
}
do:
student: (parent, { roll }) => {
return students.find((s) => s.roll === roll);
}
I mocked the "createTask" function in the "api":
import configureMockStore from "redux-mock-store";
import { createTask } from "../../actions/tasks";
import * as api from "../../api";
import thunk from "redux-thunk";
jest.mock('../../api')
// mock api module
api.createTask = jest.fn(() => {
return Promise.resolve({data : 'foo'})
})
// configuration mock store
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
// suite tests
describe("create task action create store<async>", () => {
test("works", () => {
const expectedActions = [
{ type: "REQUEST_STARTED" },
{
type: "CREATE_TASK_SUCCEED",
payLaod: { task: "foo" },
meta: { analytics: { event: "create_task", data: { id: undefined } } },
},
];
const store = mockStore({
tasks: {
tasks: [],
},
});
return store.dispatch(createTask({})).then(() => {
expect(store.getActions()).isEqual(expectedActions);
expect(api.createTask).toHaveBeenCalled();
});
});
});
But after running the test, I get this error. seem my mock function does not return a promise.
● create task action create store<async> › works
TypeError: Cannot read properties of undefined (reading 'then')
23 | return (dispatch) => {
24 | dispatch(requestStarted());
> 25 | api
| ^
26 | .createTask({ title, description, status, timer, projectId })
27 | .then((resp) => {
28 | dispatch(createTaskSucceed(resp.data));
I do not know the reason for this. Can anyone help me?
createTask action creator code
function createTask({
title,
description,
status = "Unstarted",
timer = 0,
projectId,
}) {
return (dispatch) => {
dispatch(requestStarted());
api
.createTask({ title, description, status, timer, projectId })
.then((resp) => {
dispatch(createTaskSucceed(resp.data));
})
.catch((error) => {
dispatch(requestFailed(error));
});
};
}
I also added action creator code to show what is happening in this function
I'm trying to test async service that is returning response object after few intertwined API calls (axios with interceptors). Right now I'm using jest-mock-axios lib but I'm open to any alternatives or pure Jest.
(I removed irrelevant parts of code, originally written in TS)
// services/persons.js
import personsAgent from '../agents/persons';
import places from './places';
[...]
const get = async ({ search = '', limit, offset }) => {
const places = await places.get({ search: '', limit: 1000, offset: 0 }); // api call to endpoint url '/places/'
const params = {
search: !!search.length ? search : null,
limit,
offset,
};
[...]
return personsAgent.getAll({ ...params }).then(resp => {
const results = sort(resp.data.results, .....).map((person, i) => {
const place = places?.data?.results.filter(.....);
return {
id: person.id,
name: person.first_name,
surname: person.last_name,
place,
};
});
return {
data: { ...resp.data, results },
status: resp.status,
};
});
};
[....]
export default {
get,
};
// agents/persons.js
import requests from '../utils/axios';
export default {
getAll: (params: object) => requests.get('/persons/', { params }),
}
// services/persons.test.js
import mockAxios from 'jest-mock-axios';
import persons from './persons';
afterEach(() => {
mockAxios.reset();
});
it('returns Persons data from API', async () => {
let catchFn = jest.fn(),
thenFn = jest.fn();
persons
.get({ search: '', limit: 10, offset: 0 })
.then(thenFn)
.catch(catchFn);
expect(mockAxios.get).toHaveBeenCalledWith('/persons/', {
params: { search: null, limit: 10, offset: 0 },
}); // FAIL - received: '/places/', { search: '', limit: 1000, offset: 0 }
let responseObj = {
data: {
results: ['test'],
},
};
mockAxios.mockResponse(responseObj);
expect(thenFn).toHaveBeenCalledWith({
data: {
results: ['test'],
},
status: 200,
});
expect(catchFn).not.toHaveBeenCalled();
});
I'm using jest-mock-axios and for my others, simpler services without additional, internal call everything is working fine, but this is problematic.
How to ignore or mock const places = await places.get() to focus on personsAgent.getAll()?
Issue right now is that I'm testing request for const places = await places.get() and there is no secondary request for personsAgent.getAll().
axios.getReqByUrl('/persons/') // null
Any ideas, examples or alternatives? Thx in advance!
I'm trying to test my resolvers but i'd like to test each field of the response, here's the code to call the response:
interface Options {
source: string;
variableValues?: Maybe<{ [key: string]: unknown | null }>;
}
let schema: GraphQLSchema;
const gCall = async ({
source,
variableValues,
}: Options): Promise<ExecutionResult> => {
if (!schema) {
schema = await createSchema();
}
return graphql({
schema,
source,
variableValues,
});
};
export default gCall;
And that's the code to test the resolver:
let connection: Connection;
const challengeMutation = `
mutation CreateChallenge($data: CreateChallengeInput!) {
createChallenge(data: $data) {
id
name
category
startDate
endDate
goal
description
}
}
`;
describe('Create Challenge', () => {
beforeAll(async () => {
connection = await databaseTestConnection();
await connection.createQueryBuilder().delete().from(Challenge).execute();
});
afterAll(async () => {
await connection.createQueryBuilder().delete().from(Challenge).execute();
await connection.close();
});
it('should create challenge', async () => {
const challenge = {
name: 'some awesome name',
category: 'distância',
startDate: new Date(2020, 7, 4).toISOString(),
endDate: new Date(2020, 7, 5).toISOString(),
goal: 5000,
description: 'some excelent challenge description',
};
const response = await gCall({
source: challengeMutation,
variableValues: {
data: challenge,
},
});
expect(response).toMatchObject({
data: {
createChallenge: {
name: challenge.name,
category: challenge.category,
startDate: challenge.startDate,
endDate: challenge.endDate,
goal: challenge.goal,
description: challenge.description,
},
},
});
});
});
What I'd like to do is test the fields separately, like this:
expect(response.data.createChallenge.name).toEqual(challenge.name);
But I'm getting the following error when I try to execute the above code:
Object is possibly 'null' or 'undefined'.
What can I do to solve this error and to make this test better?
Object is possibly 'null' or 'undefined'.
TypeScript warns you that the response data might not exist as the graphql "server" might return error instead. So you should use ! operator to assert it's not null.
You should also do that after checking it's not undefined with expect().
I am using redux-thunk and not sure if side effects (showAlertError function) are structured properly. Although my jest test setup seems to be fine at first glance, I get an error:
jest.fn() value must be a mock function or spy. Received: undefined`
Is the showAlertError function is at the right place or it should be in the action creator or somewhere else? Also if this is the right place for it then how I can test if it's called.
export const submitTeammateInvitation = (data) => {
const config = {
// config code
};
return async (dispatch) => {
dispatch(submitTeammateInvitationRequest(data));
try {
const response = await fetch(inviteTeammateEndpoint, config);
const jsonResponse = await response.json();
if (!response.ok) {
showErrorAlert(jsonResponse);
dispatch(submitTeammateInvitationError(jsonResponse));
throw new Error(response.statusText);
}
dispatch(submitTeammateInvitationSuccess(jsonResponse));
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.log('Request failed', error);
}
}
};
};
test
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { showAlertError } from '../../../../_helpers/alerts';
jest.mock('../../../../_helpers/alerts');
const middlewares = [thunk];
const createMockStore = configureMockStore(middlewares);
describe('submitTeammateInvitation', () => {
it('dispatches the correct actions on a failed fetch request', () => {
fetch.mockResponse(
JSON.stringify(error),
{ status: 500, statusText: 'Internal Server Error' }
);
const store = createMockStore({});
const expectedActions = [
submitTeammateInvitationRequestObject,
submitTeammateInvitationErrorObject
];
const showAlertError = jest.fn();
return store.dispatch(submitTeammateInvitation(inviteTeammateEndpoint))
.then(() => {
expect(showAlertError).toBeCalled(); // this doesn't work
expect(store.getActions()).toEqual(expectedActions); // this works
});
});
});
You can mock showErrorAlert function manually. Here is the solution:
actionCreators.ts:
import fetch from 'node-fetch';
import { showErrorAlert } from './showErrorAlert';
const SUBMIT_TEAMATE_INVITATION_REQUEST = 'SUBMIT_TEAMATE_INVITATION_REQUEST';
const SUBMIT_TEAMATE_INVITATION_SUCCESS = 'SUBMIT_TEAMATE_INVITATION_SUCCESS';
const SUBMIT_TEAMATE_INVITATION_ERROR = 'SUBMIT_TEAMATE_INVITATION_ERROR';
export const submitTeammateInvitationRequest = data => ({ type: SUBMIT_TEAMATE_INVITATION_REQUEST, payload: { data } });
export const submitTeammateInvitationSuccess = data => ({ type: SUBMIT_TEAMATE_INVITATION_SUCCESS, payload: { data } });
export const submitTeammateInvitationError = data => ({ type: SUBMIT_TEAMATE_INVITATION_ERROR, payload: { data } });
export const submitTeammateInvitation = data => {
const config = {
// config code
};
const inviteTeammateEndpoint = 'https://github.com/mrdulin';
return async dispatch => {
dispatch(submitTeammateInvitationRequest(data));
try {
const response = await fetch(inviteTeammateEndpoint, config);
const jsonResponse = await response.json();
if (!response.ok) {
showErrorAlert(jsonResponse);
dispatch(submitTeammateInvitationError(jsonResponse));
throw new Error(response.statusText);
}
dispatch(submitTeammateInvitationSuccess(jsonResponse));
} catch (error) {
if (process.env.NODE_ENV === 'development') {
console.log('Request failed', error);
}
}
};
};
showErrorAlert.ts:
export function showErrorAlert(jsonResponse) {
console.log(jsonResponse);
}
actionCreators.spec.ts:
import {
submitTeammateInvitation,
submitTeammateInvitationRequest,
submitTeammateInvitationSuccess,
submitTeammateInvitationError
} from './actionCreators';
import createMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import fetch from 'node-fetch';
import { AnyAction } from 'redux';
import { showErrorAlert } from './showErrorAlert';
const { Response } = jest.requireActual('node-fetch');
jest.mock('node-fetch');
jest.mock('./showErrorAlert.ts', () => {
return {
showErrorAlert: jest.fn()
};
});
const middlewares = [thunk];
const mockStore = createMockStore<any, ThunkDispatch<any, any, AnyAction>>(middlewares);
describe('submitTeammateInvitation', () => {
it('dispatches the correct actions on a failed fetch request', () => {
const mockedResponse = { data: 'mocked response' };
const mockedJSONResponse = JSON.stringify(mockedResponse);
const mockedData = { data: 'mocked data' };
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
new Response(mockedJSONResponse, { status: 500, statusText: 'Internal Server Error' })
);
const intialState = {};
const store = mockStore(intialState);
const expectedActions = [
submitTeammateInvitationRequest(mockedData),
submitTeammateInvitationError(mockedResponse)
];
return store.dispatch(submitTeammateInvitation(mockedData)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(showErrorAlert).toBeCalledWith(mockedResponse);
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/47560126/actionCreators.spec.ts
submitTeammateInvitation
✓ dispatches the correct actions on a failed fetch request (11ms)
-------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files | 89.29 | 50 | 83.33 | 90.91 | |
actionCreators.ts | 89.29 | 50 | 83.33 | 90.91 | 32,35 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.864s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47560126