I am a beginner with Jest, I am doing a program to study more JS. The tests work, but could this try / catch be replaced by exceptions? I believe that it is still not the best way to do this test with Jest.
import Category from "../app/models/Category.js"
describe("Test for category", () => {
it("error for null category", () => {
try {
new Category(null)
console.log("Error: category was created even as null name")
} catch (err) {
expect(err.message)
}
})
it("Error for empty category", () => {
try {
new Category(" ")
console.log("Error: category was created with an empty field")
} catch (err) {
expect(err.message)
}
})
it("Category registration", () => {
try {
new Category("Devops")
console.log("Category was created successfully!")
} catch (err) {
expect(err.message)
}
})
})
This is my class:
import { isEmpty, isNull } from "../validate.js"
export default class Category {
constructor(name) {
this.name = name
}
set name(name) {
if (isEmpty(name) || isNull(name)) throw new Error(`The category field needs to be filled`)
this._name = name
}
get name() {
return this._name
}
}
validate.js
export const isEmpty = value => !notEmpty(value)
export const isNull = value => value === null
Thanks for any help!
Use .toThrow(error?)
Note: You must wrap the code in a function, otherwise the error will not be caught and the assertion will fail.
Category.js:
import { isEmpty, isNull } from './validate';
export default class Category {
constructor(name) {
this.name = name;
}
set name(name) {
if (isEmpty(name) || isNull(name)) throw new Error(`The category field needs to be filled`);
this._name = name;
}
get name() {
return this._name;
}
}
Category.test.js:
import Category from './Category';
describe('Test for category', () => {
it('error for null category', () => {
expect(() => new Category(null)).toThrowError('The category field needs to be filled');
});
it('Error for empty category', () => {
expect(() => new Category(' ')).toThrowError('The category field needs to be filled');
});
it('Category registration', () => {
const c = new Category('Devops');
expect(c._name).toBe('Devops');
});
});
unit test result with coverage report:
PASS src/stackoverflow/64217332/Category.test.js
Test for category
✓ error for null category (11ms)
✓ Error for empty category (2ms)
✓ Category registration (1ms)
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 92.31 | 100 | 83.33 | 88.89 | |
Category.js | 85.71 | 100 | 66.67 | 83.33 | 13 |
validate.js | 100 | 100 | 100 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 5.502s, estimated 17s
Related
Considering the example below
//AuthenticatorService.js
const UserService = require("./UserService");
class AuthenticatorService{
constructor(){
//Some initialisation
}
//Some methods
}
module.exports = new AuthenticatorService();
//UserService.js
class UserService{
constructor(){
//Again some initialisations
}
}
module.exports = new UserService();
So when I try to mock the UserService class in my AuthenticatorService.spec.js file the constructor of the UserService is getting executed as it is being exported in that way. But I don't want that to be executed. Is it possible to mock the UserService module inside AuthenticatorService.spec.js file without invoking its constructor.
You could use jest.mock(moduleName, factory, options) to do this.
E.g.
UserService.js:
class UserService {
constructor() {
console.log('initialize UserService');
}
hello() {
console.log('user hello real implementation');
}
}
module.exports = new UserService();
AuthenticatorService.js:
const userService = require('./UserService');
class AuthenticatorService {
constructor() {
console.log('initialize AuthenticatorService');
}
hello() {
userService.hello();
}
}
module.exports = new AuthenticatorService();
AuthenticatorService.test.js:
const authenticatorService = require('./AuthenticatorService');
const userService = require('./UserService');
jest.mock('./UserService', () => {
return { hello: jest.fn() };
});
describe('62967707', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', () => {
userService.hello.mockImplementationOnce(() => console.log('user hello mocked implementation'));
authenticatorService.hello();
expect(userService.hello).toBeCalledTimes(1);
});
});
unit test result:
PASS stackoverflow/62967707/AuthenticatorService.test.js (12.636s)
62967707
✓ should pass (11ms)
console.log
initialize AuthenticatorService
at new AuthenticatorService (stackoverflow/62967707/AuthenticatorService.js:5:13)
console.log
user hello mocked implementation
at Object.<anonymous> (stackoverflow/62967707/AuthenticatorService.test.js:13:60)
-------------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
AuthenticatorService.js | 100 | 100 | 100 | 100 |
-------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.703s
As you can see in the logs, the real constructor of UserService will not execute. And we mocked hello method of the UserService class's instance.
I have a function, lets call it generateName, which as you’ve guessed it, generates a name. The problem is that a new name is generated each time time a test is ran.
In one of my tests, I assert that a function is called with an object containing this name. However, the name keeps on changing. I could just check that the object has property name, but I don’t really want to do that.
My idea is that I can mock the return value of the generateName function and do something like this
Import { generateName } from ‘libs/generateName’
jest.fn(generateName).mockResolvedValue ( ‘hello’ )
expect ( spy ).toHaveBeenCalledWith (
expect.objectContaining ( {
name: 'houses',
} )
)
You can use jest.mock(moduleName, factory, options) to mock libs/generateName module.
E.g.
generateName.ts:
export async function generateName() {
const name = Math.random() + '';
return name;
}
main.ts:
import { generateName } from './generateName';
export function main() {
return generateName();
}
main.test.ts:
import { main } from './main';
import { generateName } from './generateName';
jest.mock('./generateName', () => {
return {
generateName: jest.fn(),
};
});
describe('61350152', () => {
it('should pass', async () => {
(generateName as jest.MockedFunction<typeof generateName>).mockResolvedValueOnce('hello');
const actual = await main();
expect(actual).toBe('hello');
});
});
unit test results with coverage report:
PASS stackoverflow/61350152/main.test.ts (28.524s)
61350152
✓ should pass (6ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
main.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 31.98s
I have two functions which I'd like to test with Jest. However, I can't get test coverage to 100% because I can't figure out how to simulate String.prototype.trimLeft being undefined. What can I do?
function trimLeft (str: string): string {
if (String.prototype.trimLeft) {
return str.trimLeft()
} else {
return str.replace(/^[\s\uFEFF\xA0]+/, '')
}
return str
// else something's wrong
}
function trimRight (str: string, type: string): string {
if (String.prototype.trimRight) {
return str.trimRight()
} else {
return str.replace(/[\s\uFEFF\xA0]+$/, '')
}
return str
}
export { trimLeft, trimRight }
First of all, the last statement return str is not reachable in these two methods. After fixing them. Here is the unit test solution:
index.ts:
function trimLeft(str: string): string {
if (String.prototype.trimLeft) {
return str.trimLeft();
} else {
return str.replace(/^[\s\uFEFF\xA0]+/, '');
}
}
function trimRight(str: string): string {
if (String.prototype.trimRight) {
return str.trimRight();
} else {
return str.replace(/[\s\uFEFF\xA0]+$/, '');
}
}
export { trimLeft, trimRight };
index.spec.ts:
import { trimLeft, trimRight } from './';
describe('59430114', () => {
describe('#trimLeft', () => {
it('t1', () => {
expect(trimLeft(' jestjs')).toBe('jestjs');
});
it('t2', () => {
Object.defineProperty(String.prototype, 'trimLeft', { value: undefined });
expect(trimLeft(' jestjs')).toBe('jestjs');
});
});
describe('#trimRight', () => {
it('t1', () => {
expect(trimRight('jestjs ')).toBe('jestjs');
});
it('t2', () => {
Object.defineProperty(String.prototype, 'trimRight', { value: undefined });
expect(trimRight('jestjs ')).toBe('jestjs');
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59430114/index.spec.ts (8.386s)
59430114
#trimLeft
✓ t1 (4ms)
✓ t2 (1ms)
#trimRight
✓ t1 (1ms)
✓ t2 (1ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 10.085s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59430114
I need to unit test the following method which takes one parameter. But I am unable to test.
Component Method:
resetAdd(qa: qaModel) {
const street = qa.children.find(temp => temp.token === 'street');
const zip = qa.children.find(temp => temp.token === 'zip');
const city = qa.children.find(temp => temp.token === 'city');
this.fGroup.controls[street.pathway].callReset();
this.fGroup.controls[zip.pathway].callReset();
this.fGroup.controls[city.pathway].callReset();
}
TestFile:
it('resetAdd Method is called', () => {
const param1= jest.fn();
expect(component.resetAdd).toHaveBeenCalledWith(param1);
});
I am not sure what's wrong and also please let me know what else test case I can write.
Here is the unit test solution, you can use jest.spyOn:
By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries.
That means when you can jest.spyOn to spy on a method of object without custom implementation, the original method of this object will be executed as usual. Beside, there is a spy on the method, so you can use assert of jest to check if it is executed or not. For your case, the method is callReset.
index.ts:
type qaModel = any;
export class SomeComponent {
private fGroup = {
controls: {
street: {
callReset() {}
},
zip: {
callReset() {}
},
city: {
callReset() {}
}
}
};
resetAdd(qa: qaModel) {
const street = qa.children.find(temp => temp.token === 'street');
const zip = qa.children.find(temp => temp.token === 'zip');
const city = qa.children.find(temp => temp.token === 'city');
this.fGroup.controls[street.pathway].callReset();
this.fGroup.controls[zip.pathway].callReset();
this.fGroup.controls[city.pathway].callReset();
}
}
index.spec.ts:
import { SomeComponent } from './';
describe('SomeComponent', () => {
it('should call resetAdd', () => {
const comp = new SomeComponent();
const streetCallResetSpy = jest.spyOn(comp['fGroup']['controls']['street'], 'callReset');
const zipCallResetSpy = jest.spyOn(comp['fGroup']['controls']['zip'], 'callReset');
const cityCallResetSpy = jest.spyOn(comp['fGroup']['controls']['city'], 'callReset');
const qaModel = {
children: [
{ token: 'street', pathway: 'street' },
{ token: 'zip', pathway: 'zip' },
{ token: 'city', pathway: 'city' }
]
};
comp.resetAdd(qaModel);
expect(streetCallResetSpy).toBeCalledTimes(1);
expect(zipCallResetSpy).toBeCalledTimes(1);
expect(cityCallResetSpy).toBeCalledTimes(1);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58818402/index.spec.ts
SomeComponent
✓ should call resetAdd (5ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.612s, estimated 8s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58818402
You are not calling resetAdd method in unit test, still expecting it should have been called.
Steps are :
Spy method which you want to be called,
call that method
then check for to have been called
it('resetAdd Method is called', () => {
const param: qaModel = null // define param of type which is expected. I dont know structure of qaModel, so for example put as null
spyOn(component, 'resetAdd') // spy first
component.resetAdd(param)
expect(component.resetAdd).toHaveBeenCalledWith(param);});
I try to figure out how to test this kind of method
// Let's say models === null when we instantiate
public initialize(mongodb: MongoDb): this {
if (!this.models) {
this.models = {
users: new models.UserModel(mongodb),
};
}
return this;
}
public getModels(): Models | null {
return this.models || null;
}
My coverage is still staying that I didn't test the if part... This is not really true because when I ask for the getModels I can test its value (so implicitly the if.
Any idea?
Here is the solution:
index.ts:
import * as models from './models';
import { MongoDb, Models } from './interfaces';
export class UserDataSource {
private models: Models | null = null;
public initialize(mongodb: MongoDb): this {
if (!this.models) {
this.models = {
users: new models.UserModel(mongodb)
};
}
return this;
}
public getModels(): Models | null {
return this.models || null;
}
}
Unit test, index.spec.ts:
import { UserDataSource } from './';
import { MongoDb } from './interfaces';
import * as models from './models';
describe('UserDataSource', () => {
const mockedMongodb: MongoDb = {};
describe('#initialize', () => {
it('should initlialize models correctly', () => {
const userDataSource = new UserDataSource();
const actualValue = userDataSource.initialize(mockedMongodb);
expect(userDataSource.getModels()).toEqual(expect.objectContaining({ users: expect.any(models.UserModel) }));
expect(actualValue).toBe(userDataSource);
});
it('should not initialize models', () => {
const userDataSource = new UserDataSource();
// tslint:disable-next-line: no-string-literal
userDataSource['models'] = [];
const actualValue = userDataSource.initialize(mockedMongodb);
expect(actualValue).toBe(userDataSource);
});
});
describe('#getModels', () => {
it('should get models correctly', () => {
const userDataSource = new UserDataSource();
const actualValue = userDataSource.getModels();
expect(actualValue).toEqual(null);
});
it('should get models correctly and not null', () => {
const userDataSource = new UserDataSource();
// tslint:disable-next-line: no-string-literal
userDataSource['models'] = [];
const actualValue = userDataSource.getModels();
expect(actualValue).toEqual([]);
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/52729002/index.spec.ts
UserDataSource
#initialize
✓ should initlialize models correctly (5ms)
✓ should not initialize models (7ms)
#getModels
✓ should get models correctly (1ms)
✓ should get models correctly and not null (1ms)
-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
52729002 | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
52729002/models | 100 | 100 | 100 | 100 | |
UserModel.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 6.157s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/52729002