How can I mock FileReader with jest? - javascript

I've been struggling over the past couple of weeks with unit testing a file upload react component with jest. Specifically, I'm trying to test whether or not the method onReadAsDataUrl is being called from FileReader in one of my methods. This is an example method I am testing:
loadFinalImage = async (file) => {
const reader = new FileReader();
reader.onloadend = () => {
this.setState({
imagePreviewUrl: reader.result,
validCard: true,
});
};
await reader.readAsDataURL(file);
}
This is how I am attempting to mock FileReader and test whether or not onReadAsDataUrl has been called:
it('is a valid image and reader.onReadAsDataUrl was called', () => {
const file = new Blob(['a'.repeat(1)], { type: 'image/png' });
wrapper = shallow(<ImageUpload />).dive();
const wrapperInstance = wrapper.instance();
const mockReader = jest.fn();
jest.spyOn('FileReader', () => jest.fn());
FileReader.mockImplementation(() => { return mockReader });
const onReadAsDataUrl = jest.spyOn(mockReader, 'readAsDataURL');
wrapperInstance.loadFinalImage(file);
expect(onReadAsDataUrl).toHaveBeenCalled();
});
After I run: yarn jest, I get the following test failure:
Cannot spyOn on a primitive value; string given.
I assume I am getting this error because I am not importing FileReader, but I am not exactly sure how I would import it or mock it because FileReader is an interface. Here is an image of the test failure:
I am a bit of a noob with jest, reactjs, and web development, but would love to learn how to conquer this problem. Some resources I have looked at so far are: Unresolved Shopify Mock of FileReader, How to mock a new function in jest, and Mocking FileReader with jasmine.
Any help would be greatly appreciated! Thank you in advance.

I personally could not get any of the jest.spyOn() approaches to work.
Using jest.spyOn(FileReader.prototype, 'readAsDataURL') kept generating a Cannot spy the readAsDataURL property because it is not a function; undefined given instead error,
and jest.spyOn(global, "FileReader").mockImplementation(...) returned a Cannot spy the FileReader property because it is not a function; undefined given instead error
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)
I hope it can help anyone else looking into this.

Quite possibly the OP has found an answer by now, but since I was facing pretty much the same problem, here's how I did it - taking input from another SO answer.
I think #Jackyef comment is the right way to go, but I don't think the call to mockImplementation you propose is correct.
In my case, the following turned out to be correct.
const readAsDataURL = jest
.spyOn(global, "FileReader")
.mockImplementation(function() {
this.readAsDataURL = jest.fn();
});
Worth noting that VSCode highlights a potential refactoring at the anonymous function. It suggests:
class (Anonymous function)
(local function)(): void
This constructor function may be converted to a class declaration.ts(80002)
I'm still relatively new to JS, so I'm afraid I can't explain what this is about, nor what refactoring should be done.

Related

Create React App changes behaviour of jest.fn() when mocking async function

I am confused about the below behaviour of jest.fn() when run from a clean CRA project created using npx create-react-app jest-fn-behaviour.
Example:
describe("jest.fn behaviour", () => {
const getFunc = async () => {
return new Promise((res) => {
setTimeout(() => {
res("some-response");
}, 500)
});;
}
const getFuncOuterMock = jest.fn(getFunc);
test("works fine", async () => {
const getFuncInnerMock = jest.fn(getFunc);
const result = await getFuncInnerMock();
expect(result).toBe("some-response"); // passes
})
test("does not work", async () => {
const result = await getFuncOuterMock();
expect(result).toBe("some-response"); // fails - Received: undefined
})
});
The above test will work as expected in a clean JavaScript project but not in a CRA project.
Can someone please explain why the second test fails? It appears to me that when mocking an async function jest.fn() will not work as expected when called within a non-async function (e.g. describe above). It will work only when called within an async function (test above). But why would CRA alter the behaviour in such a way?
The reason for this is, as I mentioned in another answer, that CRA's default Jest setup includes the following line:
resetMocks: true,
Per the Jest docs, that means (emphasis mine):
Automatically reset mock state before every test. Equivalent to
calling jest.resetAllMocks() before each test. This will lead to
any mocks having their fake implementations removed but does not
restore their initial implementation.
As I pointed out in the comments, your mock is created at test discovery time, when Jest is locating all of the specs and calling the describe (but not it/test) callbacks, not at execution time, when it calls the spec callbacks. Therefore its mock implementation is pointless, as it's cleared before any test gets to run.
Instead, you have three options:
As you've seen, creating the mock inside the test itself works. Reconfiguring an existing mock inside the test would also work, e.g. getFuncOuterMock.mockImplementation(getFunc) (or just getFuncOuterMock.mockResolvedValue("some-response")).
You could move the mock creation and/or configuration into a beforeEach callback; these are executed after all the mocks get reset:
describe("jest.fn behaviour", () => {
let getFuncOuterMock;
// or `const getFuncOuterMock = jest.fn();`
beforeEach(() => {
getFuncOuterMock = jest.fn(getFunc);
// or `getFuncOuterMock.mockImplementation(getFunc);`
});
...
});
resetMocks is one of CRA's supported keys for overriding Jest configuration, so you could add:
"jest": {
"resetMocks": false
},
into your package.json.
However, note that this can lead to false positive tests where you expect(someMock).toHaveBeenCalledWith(some, args) and it passes due to an interaction with the mock in a different test. If you're going to disable the automatic resetting, you should also change the implementation to create the mock in beforeEach (i.e. the let getFuncOuterMock; example in option 2) to avoid state leaking between tests.
Note that this is nothing to do with sync vs. async, or anything other than mock lifecycle; you'd see the same behaviour with the following example in a CRA project (or a vanilla JS project with the resetMocks: true Jest configuration):
describe("the problem", () => {
const mock = jest.fn(() => "foo");
it("got reset before I was executed", () => {
expect(mock()).toEqual("foo");
});
});
● the problem › got reset before I was executed
expect(received).toEqual(expected) // deep equality
Expected: "foo"
Received: undefined

How to mock JSON file in Jest

I am using Jest to test my API and when I run my tests, my JSON file results.json gets written to due to the following line in my API app.js (which I don't want happening):
fs.writeFile('results.json', JSON.stringify(json), (err, result) => {
if (err) console.log('error', err);
});
This is what my Jest file looks like:
const request = require('supertest');
const app = require('./app');
// Nico Tejera at https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object
function serialise(obj){
return Object.keys(obj).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`).join('&');
}
describe('Test /addtask', () => {
test('POST /addtask Successfully redirects if newDate and newTask filled in correctly', () => {
const params = {
newTask: 'Example',
newDate: '2020-03-11'
};
return request(app)
.post('/addtask')
.send(serialise(params))
.expect(301);
});
});
I tried creating a mock of the JSON file and placed it outside the describe statement to prevent the actual results.json file being written to:
jest.mock('./results.json', () => ({ name: 'preset1', JSONtask: [], JSONcomplete: [] }, { name: 'preset2', JSONtask: [], JSONcomplete: [] }));
But this doesn't change anything. Does anyone have any suggestions?
I have seen other solutions to similar problems but they don't provide the answer I'm looking for.
EDIT: Although not a very good method, one solution to my problem is to wrap the fs.writeFile within the statement
if (process.env.NODE_ENV !== 'test') {
//code
};
although this would mean that fs.writeFile cannot be tested upon.
NOTE: I am still accepting answers!
Your issue is that the code you want to test has a hard-coded I/O operation in it, which always makes things harder to test.
What you'll want to do is to isolate the dependency on fs.writeFile, for example into something like a ResultsWriter. That dependency can then be injected and mocked for your test purposes.
I wrote an extensive example on a very similar case with NestJS yesterday under how to unit test a class extending an abstract class reading environment variables, which you can hopefully adapt to your needs.
jest.mock(path, factory) is for mocking JS modules, not file content.
You should instead mock fs.writeFile and check that it has been called with the expected arguments. The docs explain how to do it.

How do you mock just certain parts of a module with jest?

I've built a calendar around moment.js and I'm working on unit tests right now.
The first problem I solved was how the date will change when the tests run, so I've been able to lock down the moment using this guidance.
Currently, I'm stuck on an error:
"TypeError: Cannot read property 'weekdaysShort' of undefined"
My code has a line: const dateHeaders = moment.weekdaysShort();
By implementing the mocked moment().format(), I've essentially lost the rest of the library.
My immediate question is how I can set up jest to let me return the array that you get from moment.weekdaysShort();
My larger question is whether I've gone down the wrong path and should come up with another strategy.
Things I've tried with unsuccessful results
Manually adding in the weekdayShort function:
const mockMoment = function() {
return {format: '2016–12–09T12:34:56+00:00'}
};
mockMoment['weekdaysShort'] = () => ['Sun', 'Mon', 'Tues']; // etc etc etc
jest.mock('moment', () => mockMoment);
Assembling a manual mock in a __mocks__ folder. I didn't go too far down this path because it started to feel like I'd have to copy/paste the entire Moment.js library into the mock. And while it'd be cool to figure out how they do what they do, that's a project for another day.
jest.spyOn - doesn't work because I'm not spying on a module.
At this point, I'm considering abandoning the Moment function for an array passed in through props. And while I'm confident that'll get me past this problem, it feels like I'm gonna hit another roadblock quickly afterwards.
Thanks in advance for the help.
Just found out the pattern I commonly use was provided in a 2016 github thread and, in all honesty, that's probably where I found it even though I don't specifically remember it :)
jest.mock('moment', () =>
const original = jest.requireActual('moment');
return {
__esModule: true,
default: {
...original,
...just the parts you want to mock
}
}
);
MomentJS and Jest don't play well together. The requireActual method will be awesome, but even providing the following yielded a component that wouldn't render.
jest.mock('moment', () =>
const original = jest.requireActual('moment');
return {
__esModule: true,
default: {
...original,
}
}
);
Ultimately, I was able to lock the date down to a single moment by mocking the Javascript Date object, on which MomentJS builds all of this functionality.
describe('Calendar', () => {
let dateNowSpy;
beforeAll(() => {
dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000);
});
afterAll(() => {
dateNowSpy.mockRestore();
});
it('does things, () => {
// all the things!
}
}

Mocking a Node Module which uses chained function calls with Jest in Node

Allow me to note that a similar question to this one can be found here, but the accepted answer's solution did not work for me. There was another question along the same lines, the answer of which suggested to directly manipulate the function's prototypes, but that was equally non-fruitful.
I am attempting to use Jest to mock this NPM Module, called "sharp". It takes an image buffer and performs image processing/manipulation operations upon it.
The actual implementation of the module in my codebase is as follows:
const sharp = require('sharp');
module.exports = class ImageProcessingAdapter {
async processImageWithDefaultConfiguration(buffer, size, options) {
return await sharp(buffer)
.resize(size)
.jpeg(options)
.toBuffer();
}
}
You can see that the module uses a chained function API, meaning the mock has to have each function return this.
The Unit Test itself can be found here:
jest.mock('sharp');
const sharp = require('sharp');
const ImageProcessingAdapter = require('./../../adapters/sharp/ImageProcessingAdapter');
test('Should call module functions with correct arguments', async () => {
// Mock values
const buffer = Buffer.from('a buffer');
const size = { width: 10, height: 10 };
const options = 'options';
// SUT
await new ImageProcessingAdapter().processImageWithDefaultConfiguration(buffer, size, options);
// Assertions
expect(sharp).toHaveBeenCalledWith(buffer);
expect(sharp().resize).toHaveBeenCalledWith(size);
expect(sharp().jpeg).toHaveBeenCalledWith(options);
});
Below are my attempts at mocking:
Attempt One
// __mocks__/sharp.js
module.exports = jest.genMockFromModule('sharp');
Result
Error: Maximum Call Stack Size Exceeded
Attempt Two
// __mocks__/sharp.js
module.exports = jest.fn().mockImplementation(() => ({
resize: jest.fn().mockReturnThis(),
jpeg: jest.fn().mockReturnThis(),
toBuffer:jest.fn().mockReturnThis()
}));
Result
Expected mock function to have been called with:
[{"height": 10, "width": 10}]
But it was not called.
Question
I would appreciate any aid in figuring out how to properly mock this third-party module such that I can make assertions about the way in which the mock is called.
I have tried using sinon and proxyquire, and they don't seem to get the job done either.
Reproduction
An isolated reproduction of this issue can be found here.
Thanks.
Your second attempt is really close.
The only issue with it is that every time sharp gets called a new mocked object is returned with new resize, jpeg, and toBuffer mock functions...
...which means that when you test resize like this:
expect(sharp().resize).toHaveBeenCalledWith(size);
...you are actually testing a brand new resize mock function which hasn't been called.
To fix it, just make sure sharp always returns the same mocked object:
__mocks__/sharp.js
const result = {
resize: jest.fn().mockReturnThis(),
jpeg: jest.fn().mockReturnThis(),
toBuffer: jest.fn().mockReturnThis()
}
module.exports = jest.fn(() => result);

Jest testing function that calls eval

I have a Vue TS project created with vue-cli 3.0.
This is the function I will test:
public hopJavascript(url: string) {
eval(url);
}
And this is my test function using Jest framework:
test('navigation using javascript', () => {
const url = "someurl";
hopJavascript(url);
expect(eval).toBeCalled();
});
Now i get this message,test failed console logging which is telling me that I need a mocked version of eval.
How can i mock eval ?
Update
It seems you can't always override eval based on your JS environment (browser or node). See the answer to "How override eval function in javascript?" for more information
Original answer
I think you can redefine eval in your test file:
global.eval = jest.fn()
You need to track your function with spyOn().
This solution should work for you.
import MyClass from '../MyClass';
test('navigation using javascript', () => {
const url = "someurl";
const spy = jest.spyOn(MyClass, 'eval');
MyClass.hopJavascript(url);
expect(spy).toHaveBeenCalled();
});

Categories

Resources