How to mock FileReader using jest - javascript

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" } });
});
});

Related

How to mock nanoid for testing?

I'm trying to mock nanoid for my testing but it doesn't seem to be working.
my function
public async createApp(appDto: ApplicationDto): Promise<string> {
const appWithToken = { ...appDto, accessToken: nanoid() };
const application = await this.applicationModel.create(appWithToken);
return application.id;
}
My test:
beforeEach(() => {
mockRepository.create.mockResolvedValueOnce({ id: mockId });
});
test("creates application and returns an id", async () => {
const mockAppDto: ApplicationDto = { email: "123#mock.com" };
const application = await applicationService.createApplication(mockAppDto);
expect(mockRepository.create).toHaveBeenCalledWith(mockAppDto); //how do I mock the nanoid here?
expect(application).toBe(mockId);
});
So basically I'm struggling to figure out how to mock the nanoid which is generated inside the function.
I've tried the following at the top of the file:
jest.mock('nanoid', () => 'mock id');
however it doesn't work at all.
Any help would be appreciated!
You didn't mock the nanoid module correctly. It uses named exports to export the nanoid function.
Use jest.mock(moduleName, factory, options) is correct, the factory argument is optional. It will create a mocked nanoid function.
Besides, you can use the mocked function from ts-jest/utils to handle the TS type.
E.g.
Example.ts:
import { nanoid } from 'nanoid';
export interface ApplicationDto {}
export class Example {
constructor(private applicationModel) {}
public async createApp(appDto: ApplicationDto): Promise<string> {
const appWithToken = { ...appDto, accessToken: nanoid() };
const application = await this.applicationModel.create(appWithToken);
return application.id;
}
}
Example.test.ts:
import { nanoid } from 'nanoid';
import { Example, ApplicationDto } from './Example';
import { mocked } from 'ts-jest/utils';
jest.mock('nanoid');
const mnanoid = mocked(nanoid);
describe('67898249', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mnanoid.mockReturnValueOnce('mock id');
const mockAppDto: ApplicationDto = { email: '123#mock.com' };
const mockApplicationModel = { create: jest.fn().mockReturnValueOnce({ id: 1 }) };
const example = new Example(mockApplicationModel);
const actual = await example.createApp(mockAppDto);
expect(actual).toEqual(1);
expect(mockApplicationModel.create).toBeCalledWith({ email: '123#mock.com', accessToken: 'mock id' });
});
});
test result:
PASS examples/67898249/Example.test.ts (9.134 s)
67898249
✓ should pass (4 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
Example.ts | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.1 s

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

Unit Testing UI-related functions

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

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

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