I'm seeing a weird error in my test file for a NestJS application. I can't seem to figure out, what went wrong: I have a test file for a TypeORM repository:
describe('UserRepository', () => {
let userRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserRepository,
],
}).compile();
userRepository = await module.get<UserRepository>(UserRepository);
});
describe('signUp', () => {
let save;
beforeEach(() => {
save = jest.fn();
userRepository.create = jest.fn().mockReturnValue({ save });
});
it('successfully signs up the user', async () => {
save.mockResolvedValue(undefined);
expect(
userRepository.createUser(mockCredentialsDto),
).resolves.not.toThrow();
});
});
});
And a repository:
#EntityRepository(User)
export class UserRepository extends Repository<User> {
async createUser(signUpDto: SignUpDto) {
const { password, email } = signUpDto;
const salt = await bcrypt.genSalt();
const encodedPassword = await bcrypt.hash(password, salt);
const user: any = this.create();
user.email = email;
user.password = encodedPassword;
user.salt = salt;
await user.save();
return user;
}
}
However, the this.create() method doesn't seem to work. It seems to return undefined and ultimately I get an error for my tests (even though the tests are all passing:
Received promise rejected instead of resolved Rejected to value:
[TypeError: Cannot set property 'email' of undefined]
Can anyone help? I can't seem to figure out, why user is undefined.
What's missing was the return statement for the test (!):
Here's an excerpt from the Jest documentation:
"Be sure to return the assertion—if you omit this return statement, your test will complete before the promise returned from fetchData is resolved and then() has a chance to execute the callback."
Here is the correct test:
it('throws a conflict exception as username already exists', () => {
save.mockRejectedValue({ code: '23505' });
return expect(
userRepository
.createUser(mockCredentialsDto),
).rejects.toThrow(ConflictException);
});
Related
I am using NestJs framework in my project. Here i want to write testcase for a service file. But i am facing an issue in feeding the value of this.userPool in the service file.I am getting userPoolId and clientId undefined error.
I tried different solutions, but nothing works for me.
service.js
export class AuthService {
private userPool: CognitoUserPool;
constructor(private readonly authConfig: AuthConfig) {
this.userPool = new CognitoUserPool({
UserPoolId: this.authConfig.userPoolId,
ClientId: this.authConfig.clientId,
});
}
authenticateUser(user: AuthCredentialsDto) {
try {
const { userName, password } = user;
const authenticateDetails = new AuthenticationDetails({
Username: userName,
Password: password,
});
const userData = {
Username: userName,
Pool: this.userPool,
};
const newUser = new CognitoUser(userData);
return new Promise((resolve, reject) => {
------------------------
--------------------------------
});
} catch (error) {
throw new BadRequestException(error.message);
}
}
service.spec.ts
import { AuthService } from './auth.service';
import {
AuthenticationDetails,
CognitoUser,
CognitoUserAttribute,
CognitoUserPool,
} from 'amazon-cognito-identity-js';
import { AuthConfig } from './cognito.service';
// const mockCognitoUserPool = () => ({
// registerUser: jest.fn(),
// });
describe('AuthService', () => {
let service: AuthService;
let authConfig: AuthConfig;
let userPool: CognitoUserPool;
userPool = new CognitoUserPool({
UserPoolId: authConfig.userPoolId,
ClientId: authConfig.clientId,
});
// let userPool;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
AuthConfig,
userPool
],
}).compile();
service = module.get<AuthService>(AuthService);
authConfig = module.get<AuthConfig>(AuthConfig);
// cognitoUserPool = module.get<CognitoUserPool>(CognitoUserPool);
});
describe('authenticateUser', () => {
it('calls registerUser and returns the result', async () => {
const mockUser = {
userName: 'usernme',
password: 'password',
};
// tasksRepository.findOne.mockResolvedValue(mockTask);
const result = await service.authenticateUser(mockUser);
expect(result).toEqual('result');
});
});
// it('should be defined', () => {
// expect(service).toBeDefined();
// });
});
Did you mean to spell your username property ‘usernme’ in the test file?
I am using mocha and sinon for test the node services, In controller I have getToken npm module for getting the token with name and value as parameters and in spec file I trying to send empty name as parameter using withargs but the response getting success excepted result is token creating fail please help on this issue.
controller.ts
import {getToken} from './tokenUtil';
export async function userInfo(req:Request,res:Response){
try{
let token = await getToken(name,value);
}
catch(error){
res.send({status:'Failue',message:error});
return
}
res.send({status:'success',message:'token creation success'})
}
tokenUtil.ts
export async function getToken(name,value){
// token code here
}
token.spce.ts
import * as sinon from 'sinon';
import * as proxyquire from 'proxyquire';
describe('get token',()=>{
let req:any;
let res:any;
beforeEach(()=>{
res={
send:sinon.stub();
}
it('getting error when given empty name',async()=>{
let tokenStub = sinon.stub().withArgs('',tokenValue).returns(undefined);
let tokenctl=proxyquire('./controller',{
'./tokenUtil':tokenStub
})
await tokenctl.userInfo(req,res);
sinon.assert.calledWithExactly(res.send,{status:'Failue',message:'token creating fail'})
})
})
})
You are testing the controller.ts module, so the test file name should be controller.spec.ts or controller.test.ts.
Since the ./tokenUtil use named exports, so the tokenStub should be an object.
You should use sinon.stub().rejects() to create a promise stub with rejected value.
E.g.
controller.ts:
import { getToken } from './tokenUtil';
import { Request, Response } from 'express';
export async function userInfo(req: Request, res: Response) {
const { name, value } = req.body;
try {
let token = await getToken(name, value);
res.send({ status: 'success', message: 'token creation success' });
} catch (error) {
res.send({ status: 'Failue', message: error });
}
}
tokenUtil.ts:
export async function getToken(name, value) {
// token code here
}
controller.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('get token', () => {
let req: any;
let res: any;
beforeEach(() => {
res = {
send: sinon.stub(),
};
});
it('should create token success', async () => {
req = { body: { value: '123', name: 'teresa teng' } };
let tokenStub = {
getToken: sinon.stub().withArgs(req.body.name, req.body.value).resolves(),
};
let tokenctl = proxyquire('./controller', {
'./tokenUtil': tokenStub,
});
await tokenctl.userInfo(req, res);
sinon.assert.calledWithExactly(res.send, { status: 'success', message: 'token creation success' });
});
it('should handle error when given empty name', async () => {
const tokenValue = '123';
req = { body: { value: tokenValue, name: '' } };
const error = new Error('token creating fail');
let tokenStub = {
getToken: sinon.stub().withArgs('', tokenValue).rejects(error),
};
let tokenctl = proxyquire('./controller', {
'./tokenUtil': tokenStub,
});
await tokenctl.userInfo(req, res);
sinon.assert.calledWithExactly(res.send, { status: 'Failue', message: error });
});
});
Test result:
get token
✓ should create token success (101ms)
✓ should handle error when given empty name
2 passing (112ms)
I need to reset global variable on custom hook when unit testing React component. I have read few tutorials and StackOverflow answers to this simple task, but without luck to implement it correctly.
The problem
userInfo is undefined in the first and second test but when runs the third test userInfo is defined then on useEffect doesn't change the value... So my question is how to reset userInfo for each test.
jest.resetModules // doesn't work
jest.isolateModules // doesn't work
My simplest possible setup for single test is as following:
My Environment
"jest": "^24.9.0",
My Hook
import {useState, useEffect} from "react";
// This variable is an object save user info
let userInfo = null;
export default (authService) => {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (userInfo !== null || authService === null) {
return;
}
setLoading(true);
authService
?.getUser()
.then((response) => {
userInfo = {owners: [{...response, cost_center: response.costCenter || "N/A"}]};
})
.catch(() => {
setError({
title: "authService Error",
message: "Error getting user",
status: 500
});
})
.finally(() => setLoading(false));
}, [authService]);
return [userInfo, error, loading];
};
My Test
import {renderHook} from "#testing-library/react-hooks";
import * as sinon from "sinon";
import {getSpyOfUseOktaAuth} from "../../../__tests__/";
import {Info, InfoFromRequest, InfoWithNoCostCenter} from "../../../__tests__/";
describe("useGetUserInfo", () => {
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
jest.useFakeTimers();
});
afterAll(() => {
clock.restore();
});
it("should set the error value after the getUserInfo function throws an error", async () => {
const useGetUserInfo = require("../index").default;
const errorMessage = {
title: "authService Error",
message: "Error getting user from",
status: 500
};
const getAuthMock = getSpyOfUseAuth({
Auth: {
signOut: jest.fn(),
getUser: jest.fn(async () => {
throw new Error("Auth Error");
})
},
authState: {}
});
const {result, rerender, waitForNextUpdate} = renderHook(() =>
useGetUserInfo(getAuthMock.results.Auth)
);
rerender();
await waitForNextUpdate();
expect(result.current[1]).toEqual(errorMessage);
getAuthMock.instance.mockRestore();
});
it("should return the user info from after run the getUserInfo function", async () => {
const useGetUserInfo = require("../index").default;
let authService = null;
const {result, rerender, waitForNextUpdate} = renderHook(() => useGetOktaUserInfo(authService));
const getAuthMock = getSpyOfUseAuth({
Auth: {
signOut: jest.fn(),
getUser: jest.fn(async () => Info)
},
authState: {}
});
authService = getAuthMock.results.Auth;
rerender();
await waitForNextUpdate();
expect(result.current[0]).toEqual(InfoFromRequest);
getAuthMock.instance.mockRestore();
});
it("should set cost_center as in data as N/A if costCenter is not defined in user info ", async () => {
const useGetUserInfo = require("../index").default;
const getAuthMock = getSpyOfUseAuth({
Auth: {
signOut: jest.fn(),
getUser: jest.fn(async () => InfoWithNoCostCenter)
},
authState: {}
});
const {result, rerender} = renderHook(() => useGetUserInfo(getAuthMock.results.Auth));
rerender();
expect(result.current[0].owners[0].cost_center).toEqual("N/A");
getAuthMock.instance.mockRestore();
});
});
I would say that either you export the 'userInfo' variable from the hook and you set it to null manually before each test, or you treat 'userInfo' as a state variable just like 'error' and 'loading'
If you go for the first option, you will need to export by reference Node Modules - exporting a variable versus exporting functions that reference it?
For the second option, it would be something like this
import {useState, useEffect} from "react";
export default (authService) => {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
if (userInfo !== null || authService === null) {
return;
}
setLoading(true);
authService
?.getUser()
.then((response) => {
setUserInfo({owners: [{...response, cost_center: response.costCenter || "N/A"}]});
})
.catch(() => {
setError({
title: "authService Error",
message: "Error getting user",
status: 500
});
})
.finally(() => setLoading(false));
}, [authService]);
return [userInfo, error, loading];
};
The solution I got is to separate each case into different files. As jest load each file in a different process, this will be enough
Model.knex(knex);
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(i18nextMiddleware);
I want to test method getUsers of users controller. It get data from usersModel getUsers method. I get date from MySQL by Objections ORM.
app.get(
'/',
checkFilter(['type']),
users.getUsers
);
According to instructions, I am changing the query method.
class MyQueryBuilder extends QueryBuilder {
query() {
return this.resolve({ test: 11111 });
}
}
class UsersModel extends Model {
static get QueryBuilder() {
return MyQueryBuilder;
}
}
jest.spyOn(Users, 'query')
.mockImplementation(UsersModel.query);
Describe test.
describe('get errors', () => {
beforeAll(done => {
i18next.on('initialized', () => {
done()
});
});
it('with filter', done => {
request(app)
.get('/')
.query({page: 0, perPage: 5, type: 'admin'})
.end((err, res) => {
if (err) return done(err);
expect(res.status).toBe(200);
expect(
Object.keys(res.body).sort()
).toEqual([
'items',
'itemsOnPage',
'currentPage',
'totalPage',
'totalItems'
].sort());
expect(res.body.items).toHaveLength(8);
expect(res.body.totalItems).toBe(usersMockDB.getUsers.length);
console.log(res.body);
done();
});
});
afterAll(done => {
knex.destroy();
done();
})
});
Method getUsers of users model.
const { Users } = require('../../db/models/Users');
const query = Users
.query()
.select(
'id',
'login',
'type',
'edit',
'email',
'phone',
'block'
)
.orderBy('id', 'DESC')
.page(page, perPage);
// filter
if (Object.keys(usersFilter).length) {
for (let field in usersFilter) {
if ( usersFilter.hasOwnProperty(field) ) {
query.where(field, 'like', `%${ usersFilter[field] }%`);
}
}
}
const { results, total } = await query;
return {
items: results,
itemsOnPage: Number(perPage),
currentPage: Number(page),
totalPage: Math.ceil(total/perPage),
totalItems: Number(total)
}
Should I override methods page and where ? As I understand it, they make new database queries.
This may not be desirable in every case, but I find that the easiest solution for unit tests that use objection models is to create one transaction per test. This does mean you'll need a database to run tests, but everything is rolled back between tests.
In jest.config.js, add this line
setupFilesAfterEnv: ['./jest.setup.js'],
in jest.setup.js:
import objection from 'objection';
import knex from './src/db/index.js'; // Change src/db/index.js to the path to the file where you export your knex instance
const {transaction, Model} = objection;
global.beforeAll(async () => {
global.knex = knex;
global.txn = null;
});
global.beforeEach(async () => {
global.txn = await transaction.start(knex);
Model.knex(global.txn);
});
global.afterEach(async () => {
await global.txn.rollback();
Model.knex(knex);
});
global.afterAll(async () => {
global.knex.destroy();
});
You can then use your models as expected in your code and unit tests
import {User} from './src/db/models/index.js';
it('creates and reads users', async () => {
const user = await User.query().insert({email: 'test#test.com'});
const users = await User.query();
expect(users).toHaveLength(1);
});
it('does not persist users between tests', async () => {
const users = await User.query();
expect(users).toHaveLength(1);
});
I am trying to test a simple hook that fetches some data using axios. However the test is throwing a TypeError: "Cannot read property 'fetchCompanies' of undefined". Here's my custom hook (the full repo is here):
import { useState, useEffect } from 'react';
import { Company } from '../../models';
import { CompanyService } from '../../services';
export const useCompanyList = (): {
loading: boolean;
error: any;
companies: Array<Company>;
} => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const [companies, setCompanies] = useState<Array<Company>>([]);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const companies = await CompanyService.fetchCompanies();
// Sort by ticker
companies.sort((a, b) => {
if (a.ticker < b.ticker) return -1;
if (a.ticker > b.ticker) return 1;
return 0;
});
setCompanies(companies);
setLoading(false);
} catch (e) {
setError(e);
}
};
fetchData();
}, []);
return { loading, error, companies };
};
and here's my test:
import { renderHook } from 'react-hooks-testing-library';
import { useCompanyList } from './useCompanyList';
const companiesSorted = [
{
ticker: 'AAPL',
name: 'Apple Inc.'
},
...
];
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
fetchCompanies: () => companiesUnsorted
};
});
describe('useCompanyList', () => {
it('returns a sorted list of companies', () => {
const { result } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});
});
Please help me understand how to use react-hooks-testing-library in this case.
Edit
This seems to be related to a Jest issue that was seemingly resolved. Please see https://github.com/facebook/jest/pull/3209.
The
TypeError: "Cannot read property 'fetchCompanies' of undefined"
is caused by the way you define the CompanyService service. In the code, you are exporting an object CompanyService with all the service methods. But in your test, you are mocking the CompanyService to return an object with the methods.
So, the mock should return a CompanyService object that is an object with all the methods:
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
CompanyService: {
fetchCompanies: () => companiesUnsorted
}
};
});
Now, once you solve this, you will find that you don't have the TypeError anymore but your test is not passing. That is because the code you are trying to test is asynchronous, but your test is not. So, immediately after you render your hook (through renderHook) result.current.companies will be an empty array.
You will have to wait for your promise to resolve. Fortunately, react-hooks-testing-library provides us a waitForNextUpdate function in order to wait for the next hook update. So, the final code for the test would look:
it('returns a sorted list of companies', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual([]);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});