Unit Testing UI-related functions - javascript

I'm trying to get unit-testing concept and get lost with testing UI-related functions, like this:
const hideElem = elem => {
elem.classList.add('js-hidden')
}
or this:
const getInputValue = (inputElem) => {
let inputEl = document.querySelector(inputElem)
return inputEl.value
}
or this:
const cleanInput = input => {
const inputEl = document.querySelector(input)
inputEl.value = ''
}
Do I need to cover such functions with unit tests in Jest? If yes - could you please explain how to combine here Jest and DOM-related things?

Unit testing should focus on code logic rather than the DOM related thing of the browser. It's NOT e2e testing. For unit testing, we can create and use mocked objects to check the code logic is correct or not.
Unit testing should not depend on any external environment, such as browsers, third-party services. In other words, we need to mock dom-related things to make the unit test code have good isolation.
For node.js environment, we can use jsdom to emulate dom-related thing and web API.
In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.
E.g.
index.js:
const hideElem = (elem) => {
elem.classList.add('js-hidden');
};
const getInputValue = (inputElem) => {
let inputEl = document.querySelector(inputElem);
return inputEl.value;
};
const cleanInput = (input) => {
const inputEl = document.querySelector(input);
inputEl.value = '';
};
export { hideElem, getInputValue, cleanInput };
index.test.js:
import { hideElem, getInputValue, cleanInput } from './';
describe('62455692', () => {
describe('hideElem', () => {
it('should hide elem', () => {
const mElem = { classList: { add: jest.fn() } };
hideElem(mElem);
expect(mElem.classList.add).toBeCalledWith('js-hidden');
});
});
describe('getInputValue', () => {
it('should get input value', () => {
const mInputElem = { value: 'mocked value' };
jest.spyOn(document, 'querySelector').mockReturnValueOnce(mInputElem);
const actual = getInputValue('#foo');
expect(actual).toBe('mocked value');
expect(document.querySelector).toBeCalledWith('#foo');
document.querySelector.mockRestore();
});
});
describe('cleanInput', () => {
it('should clean input', () => {
const mInputElem = { value: 'mocked value' };
jest.spyOn(document, 'querySelector').mockReturnValueOnce(mInputElem);
cleanInput('#foo');
expect(mInputElem.value).toBe('');
expect(document.querySelector).toBeCalledWith('#foo');
document.querySelector.mockRestore();
});
});
});
unit test result:
PASS stackoverflow/62455692/index.test.js (10.489s)
62455692
hideElem
✓ should hide elem (5ms)
getInputValue
✓ should get input value (2ms)
cleanInput
✓ should clean input (1ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 11.874s, estimated 13s

Related

How to mock moment() in jest testing

I need run some testing using jest and moment. Some of the imported functions work with moment current date (moment() ). But I can't seem to find a way of always run the same date for testing or mock moment constructor, like pin moment date at moment('2020-07-05'), even if current day is 2020-07-10, so the test should always run under day 5.
My ./UtilsModule file:
import moment from 'moment'
export const getIntervalDates = groupOfDates => {
const CURRENT_DATE = moment().format('YYYY-MM-DD');
return getDates(CURRENT_DATE, groupOfDates ) //another function that does some extra processing;
};
export const nextDates = (date, group) => {
let newDate= getIntervalDates(group);
}
My test.js file, and what i tried for:
import { nextDates ,getIntervalDates } from '../UtilsModule';
it('testing function', () => {
const PAYLOAD = {...};
const DATE= '2020-07-20';
const SpyGetIntervalDates = jest.spyOn(UtilsModule, 'getIntervalDates');
SpyGetIntervalDates.mockImplementation(() => Promise.resolve({ minAge: '2020-08-04' }));
const nextDate = UtilsModule.nextDates(DATE, PAYLOAD);
expect(nextDate).toEqual({ minDate: '2020-11-04' });
});
I also tried, but i couldn't make it to work :
jest.mock('moment', () => {
return () => jest.requireActual('moment')('2020-07-04');
});
and
global.moment = jest.fn(moment('2021-07-04'));
You were trying to test nextDates function with mocked getIntervalDates function. You need to do a little refactoring, you should keep the same reference for getIntervalDates function called inside nextDates. Then, you can use jest.spyOn to replace getIntervalDates with a mocked one.
E.g.
utilsModule.js:
import moment from 'moment';
function getDates() {}
const getIntervalDates = (groupOfDates) => {
const CURRENT_DATE = moment().format('YYYY-MM-DD');
return getDates(CURRENT_DATE, groupOfDates);
};
const nextDates = (date, group) => {
return exports.getIntervalDates(group);
};
exports.getIntervalDates = getIntervalDates;
exports.nextDates = nextDates;
utilsModule.test.js:
const UtilsModule = require('./utilsModule');
describe('62736904', () => {
it('testing function', async () => {
const PAYLOAD = {};
const DATE = '2020-07-20';
const SpyGetIntervalDates = jest.spyOn(UtilsModule, 'getIntervalDates');
SpyGetIntervalDates.mockImplementation(() => Promise.resolve({ minAge: '2020-08-04' }));
const nextDate = await UtilsModule.nextDates(DATE, PAYLOAD);
expect(nextDate).toEqual({ minAge: '2020-08-04' });
});
});
unit test result:
PASS stackoverflow/62736904/utilsModule.test.js (11.51s)
62736904
✓ testing function (3ms)
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 75 | 100 | 33.33 | 75 |
utilsModule.js | 75 | 100 | 33.33 | 75 | 6-7
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.892s

Using Jest.fn() to see if s3.upload function was called...what am I doing wrong?

I am a bit new to testing and I have been stuck on this issue for quite some time. So I am trying to test a s3.upload() function to see if it called, not to see if it actually uploads the object. The only constraint is that I cannot use any npm packages to mock out the functionality of the s3 bucket.
I was trying to follow this tutorial (How to mock a function inside another function (which I am testing) using sinon?) that uses sinon as a stub, but instead use jest instead. Any help or guidance with issue is appreciated.
// function.js
const uploadToS3 = (params) => {
const response = s3.upload(params).promise();
return response;
}
// functions.test.js
describe("Lambda Handler Function", () => {
test('To test to see if the uploadToS3 function was called', () => {
const sampleParam = {
Bucket: 'BucketName',
Key: 'BucketKey.zip',
Body: 'SGVsbG8sIFdvcmxk'
}
expect(uploadToS3(sampleParam).response).toBeCalled()
})
})
You can use jest.mock(moduleName, factory, options) to mock aws-sdk.
E.g.
function.js:
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
const uploadToS3 = async (params) => {
const response = await s3.upload(params).promise();
return response;
};
export { uploadToS3 };
function.test.js:
import { uploadToS3 } from './function';
import AWSMock from 'aws-sdk';
jest.mock('aws-sdk', () => {
const mS3 = { upload: jest.fn().mockReturnThis(), promise: jest.fn() };
return { S3: jest.fn(() => mS3) };
});
describe('60970919', () => {
it('should pass', async () => {
const mS3 = new AWSMock.S3();
const mResponse = { Bucket: 'xxx' };
mS3.upload({}).promise.mockResolvedValueOnce(mResponse);
const actual = await uploadToS3({});
expect(actual).toEqual(mResponse);
expect(mS3.upload).toBeCalledWith({});
expect(mS3.upload().promise).toBeCalled();
});
});
unit test results with 100% coverage:
PASS stackoverflow/60970919/function.test.js (13.818s)
60970919
✓ should pass (9ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
function.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.486s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60970919

How to mock FileReader using jest

I need to mock a function that uses FileReader using jest.
Specifically the function readAsBinaryString and onload.
I've created some code:
FileReader.readAsBinaryString = () => mock.mockReturnValue(null);
But it doesn't work.
How can I mock FileReader and your functions using jest?
Function to test:
handleFileUpload(event) {
let reader = new FileReader();
let file = event.target.files[0];
reader.readAsBinaryString(file);
reader.onload = () => {
let base64String = btoa(reader.result);
this.object.image =
};
},
You can use jest.spyOn(object, methodName, accessType?) to spy on readAsBinaryString method of FileReader. readAsBinaryString is an instance method, not static method of FileReader constructor. Besides, the return value of readAsBinaryString is void. So you can't mock a return value.
E.g.
index.ts:
export function main() {
const fr = new FileReader();
const blob = new Blob();
fr.readAsBinaryString(blob);
}
index.spec.ts, we need spy on FileReader.prototype.readAsBinaryString, since it's an instance method.
import { main } from './';
describe('main', () => {
test('should mock FileReader', () => {
const readAsBinaryStringSpy = jest.spyOn(FileReader.prototype, 'readAsBinaryString');
main();
expect(readAsBinaryStringSpy).toBeCalledWith(new Blob());
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58644737/index.spec.ts
main
✓ should mock FileReader (10ms)
----------|----------|----------|----------|----------|-------------------|
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: 4.852s, estimated 9s
Update
index.ts:
export class Component {
object = {
image: ''
};
handleFileUpload(event) {
let reader = new FileReader();
let file = event.target.files[0];
reader.readAsBinaryString(file);
reader.onload = () => {
let base64String = btoa(reader.result as string);
this.object.image = base64String;
};
return reader;
}
}
index.spec.ts:
import { Component } from './';
const cmp = new Component();
describe('main', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
test('should test handle file upload correctly', () => {
const mFile = new File(['go'], 'go.pdf');
const mEvent = { target: { files: [mFile] } };
const readAsBinaryStringSpy = jest.spyOn(FileReader.prototype, 'readAsBinaryString');
const btoaSpy = jest.spyOn(window, 'btoa');
const reader = cmp.handleFileUpload(mEvent);
expect(reader).toBeInstanceOf(FileReader);
if (reader.onload) {
Object.defineProperty(reader, 'result', { value: 'gogo' });
const mOnloadEvent = {} as any;
reader.onload(mOnloadEvent);
expect(btoaSpy).toBeCalledWith('gogo');
expect(cmp.object.image).toBe(btoa('gogo'));
}
expect(readAsBinaryStringSpy).toBeCalledWith(mFile);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/58644737/index.spec.ts (7.328s)
main
✓ should test handle file upload correctly (13ms)
----------|----------|----------|----------|----------|-------------------|
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: 8.78s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58644737
I've made some progress:
const dummy = {
readAsBinaryString: jest.fn(),
onload: function(){
wrapper.vm.object.image = '...'
}
}
}
window.FileReader = jest.fn(() => dummy)
The problem is that onload isn't get mocked on real call:
reader.onload = function() {
}
Only when I call
reader.onload()
So I think onload declaration on dummy is wrong.
I personally could not get any of the jest.spyOn() approaches to work in my Vue-test-utils setup, jest.spyOn(FileReader.prototype, 'readAsDataURL'); kept generating the following error:
Cannot spy the readAsDataURL property because it is not a function; undefined given instead
If it can help anyone else having issues with this, I managed to successfully mock the FileReader prototype using the following:
Object.defineProperty(global, 'FileReader', {
writable: true,
value: jest.fn().mockImplementation(() => ({
readAsDataURL: jest.fn(),
onLoad: jest.fn()
})),
})
Then in my test, I was able to test the file input onChange method (which was making use of the FileReader) by mocking the event and triggering it manually like this:
const file = {
size: 1000,
type: "audio/mp3",
name: "my-file.mp3"
}
const event = {
target: {
files: [file]
}
}
wrapper.vm.onChange(event)
Pretty late comment, but for what it's worth, this his how I was able to mock FileReader:
First, I created a function that returned new FileReader() instead of calling it directly.
export function fileReaderWrapper() {
return new FileReader();
}
Then the code that needs file reader can call that function
const reader = fileReaderWrapper();
reader.readAsDataURL(file);
while (reader.readyState !== 2) {
yield delay(100);
}
const imageData = reader.result;
Now my test can use jest to mock everything I check.. and I no longer have to use a timeout in my test to wait on FileReader to finish reading a file.
jest.mock('~/utils/helper', () => ({
fileReaderWrapper: jest.fn().mockReturnValue({
readAsDataURL: (file: File) => {
return;
},
readyState: 2,
result: 'data:image/png;base64,undefined',
}),
}));
if you want to call the onload event as a part of readAsText You can use following code
const readAsTextMock = jest.fn();
jest.spyOn(global, 'FileReader').mockImplementation(function () {
const self = this;
this.readAsText = readAsTextMock.mockImplementation(() => {
self.onload({ target: { result: "file read result mock" } });
});
});

How do I spyOn third party function with jest?

I am having trouble mocking a third party dependency. I'm always recieving this error:
Cannot spy the undefined property because it is not a function;
undefined given instead
Here are the details of this issue. First, this is the function I am testing:
File: src/js/mp_wrapper.js
import { Viewer } from 'third-party';
module.exports = {
createViewer: container => {
if (util.isElement(container)) {
return new Viewer(container);
} else {
throw new Error(
'Invalid Element when attempting to create underlying viewer.',
);
}
},
}
Looking at the source code for my third-party, Viewer is very simple and looks like this:
function Viewer(){
// Doing things
}
Viewer.prototype.foo = function(){
}
module.exports = Viewer;
And finally, here is my test.
File: /tests/mp_wrapper.spec.js
import { Viewer } from 'third-party`;
import mp_wrapper from '../src/js/mp_wrapper';
describe('mp_wrapper', () => {
describe('createViewer', () => {
test('returns a new instance of the Viewer class', () => {
const spy = jest.spyOn(Viewer).mockImplementation(() => jest.fn());
// It fails on the line above... -> "Cannot spy the undefined property because it is not a function; undefined given instead"
const testElement = document.createElement(testElement);
let viewer = mp_wrapper.createViewer(testElement);
expect(spy).toHaveBeenCalled();
expect(viewer).toBeInstancecOf(Viewer);
spy.mockRestore();
});
});
});
How can I mock & spy on Viewer itself?
I've done this in the past:
const spy = jest.spyOn(Viewer.prototype, 'foo').mockImplementation(() => jest.fn());
I've also tried default with no luck:
const spy = jest.spyOn(Viewer, 'default').mockImplementation(() => jest.fn());
But now I want to spy on Viewer.
EDIT:
This was my final solution. #brian-lives-outdoors answer is correct, but I didn't describe my problem accurately. The third party library I was trying to mock was slightly more complex because it export a module containing several constructors. It looked like this:
module.exports = {
Viewer: require('./path/Viewer'),
Foo: require('./foo_path/Foo'),
Bar: require('./bar_path/Bar')
}
Then inside ./path/Viewer is was as I previously described above.
Here's what my solution ended up looking like:
import { Viewer } from 'lib';
import mp_wrapper from '../src/js/mp_wrapper';
jest.genMockFromModule('lib');
jest.mock('lib');
describe('mp_wrapper', () => {
describe('createViewer', () => {
test('returns a new instance of the Viewer class and sets the local _viewer property', () => {
const testContainer = document.createElement('div');
const viewer = mp_wrapper.createViewer(testContainer);
expect(Viewer).toHaveBeenCalledWith(testContainer); // Success!
expect(viewer).toBeInstanceOf(Viewer); // Success!
});
});
});
#brian-lives-outdoors What I don't understand is if I comment out the line jest.mock('lib'); above, it doesn't work...Why?
Why isn't genMockFromModule sufficient by itself?
The example code mixes ES6 import/ export syntax with Node module.exports syntax...
...but based on a library that looks like this:
lib.js
function Viewer() { }
Viewer.prototype.foo = function () { }
module.exports = Viewer;
...it would be used like this:
mp_wrapper.js
import Viewer from './lib'; // <= Babel allows Viewer to be used like an ES6 default export
export const createViewer = container => new Viewer(container);
...and to spy on Viewer you would need to mock the entire library in your test:
mp_wrapper.spec.js
import Viewer from './lib';
import { createViewer } from './mp_wrapper';
jest.mock('./lib', () => jest.fn()); // <= mock the library
test('returns a new instance of the Viewer class', () => {
const viewer = createViewer('the container');
expect(Viewer).toHaveBeenCalledWith('the container'); // Success!
expect(viewer).toBeInstanceOf(Viewer); // Success!
});
Note that if the library was an ES6 library then you could spy on the default export directly like this:
import * as lib from './lib';
const spy = jest.spyOn(lib, 'default'); // <= spy on the default export
...but because of the way Babel handles the interop between ES6 and non-ES6 code this approach doesn't work if the library is not ES6.
Edit: response to the follow-up question
jest.genMockFromModule generates a mocked version of the module and returns it.
So for example this:
const mock = jest.genMockFromModule('lib');
...generates a mocked version of lib and assigns it to mock. Note that this does not mean that the mock will be returned when lib is required during a test.
jest.genMockFromModule can be useful when creating a manual mock:
__mocks__/lib.js
const lib = jest.genMockFromModule('lib'); // <= generate a mock of the module
lib.someFunc.mockReturnValue('some value'); // <= modify it
module.exports = lib; // <= export the modified mock
In your final solution you have these two lines:
jest.genMockFromModule('lib');
jest.mock('lib');
This line:
jest.genMockFromModule('lib');
...doesn't actually do anything since it is generating a mock of the module but the returned mock isn't being used for anything.
This line:
jest.mock('lib');
...tells Jest to auto-mock the lib module and is the only line that is needed in this case.
Here is a solution:
util.js
const util = {
isElement() {}
};
module.exports = util;
View.js, the third-party module:
function Viewer() {
// Doing things
console.log('new viewer instance');
}
Viewer.prototype.foo = function() {};
module.exports = { Viewer };
my_wrapper.js:
const { Viewer } = require('./viewer');
const util = require('./util');
module.exports = {
createViewer: container => {
if (util.isElement(container)) {
return new Viewer(container);
} else {
throw new Error('Invalid Element when attempting to create underlying viewer.');
}
}
};
Unit test:
const { Viewer } = require('./viewer');
const my_wrapper = require('./');
const util = require('./util');
jest.mock('./viewer', () => {
return {
Viewer: jest.fn()
};
});
describe('mp_wrapper', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('createViewer', () => {
it('t1', () => {
util.isElement = jest.fn().mockReturnValueOnce(true);
let viewer = my_wrapper.createViewer('el');
expect(util.isElement).toBeCalledWith('el');
expect(viewer).toBeInstanceOf(Viewer);
});
it('t2', () => {
util.isElement = jest.fn().mockReturnValueOnce(false);
expect(() => my_wrapper.createViewer('el')).toThrowError(
new Error('Invalid Element when attempting to create underlying viewer.')
);
expect(Viewer).not.toBeCalled();
});
});
});
Unit test result:
PASS src/stackoverflow/57712713/index.spec.js
mp_wrapper
createViewer
✓ t1 (6ms)
✓ t2 (5ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 50 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
util.js | 100 | 100 | 0 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.134s, estimated 9s

Chained API's and Jest expects

I currently have an express app that does a bunch of logic on a controller.
One of the steps is to insert a record to the DB ( It uses ObjectionJS models ).
let user = await this.User.query(trx).insert(userData);
In an attempt to mock out the model, I have done :
let mockUser = {
query: jest.fn(() => {
return mockUser;
}),
insert: jest.fn(() => {
return mockUser;
}),
toJSON: jest.fn()
};
With this, I wanted to do an assertion:
expect(mockUser.query().insert).toBeCalledWith({ some: 'data' });
It seems I have missed something. When I run the tests, the code would reach the mock function insert. But jest complaints
You could use mockFn.mockReturnThis() to return this context.
E.g.
index.js:
export async function main(User) {
const trx = 'the trx';
const userData = {};
let user = await User.query(trx).insert(userData);
return user.toJSON();
}
index.test.js:
import { main } from './';
describe('47953161', () => {
it('should pass', async () => {
let mockUser = {
query: jest.fn().mockReturnThis(),
insert: jest.fn().mockReturnThis(),
toJSON: jest.fn().mockResolvedValueOnce({ id: 1 }),
};
const actual = await main(mockUser);
expect(actual).toEqual({ id: 1 });
expect(mockUser.query).toBeCalledWith('the trx');
expect(mockUser.query().insert).toBeCalledWith({});
expect(mockUser.query().insert().toJSON).toBeCalledTimes(1);
});
});
unit test result with coverage report:
PASS src/stackoverflow/47953161/index.test.ts (10.41s)
47953161
✓ should pass (7ms)
----------|----------|----------|----------|----------|-------------------|
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: 12.783s, estimated 13s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47953161

Categories

Resources