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

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!
}
}

Related

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 I mock Date.toLocaleDateString in jest?

I have this codepiece in my React component, which renders an HTML in the end:
new Date(createDate).toLocaleDateString()
My local machine and our build machine have different locales set, so the result of this function is not coherent. So as you'd expect, the unit test passes on my machine and fails on build machine, or vice versa.
I want to mock "toLocalDateString" so that it always uses the same locale, say 'en-US', or at least it always returns the same string. Our test framework is jest. How do I achieve this goal?
I tried this in my test.spec.js but it didn't have any effect at all:
Date.prototype.toLocaleDateString = jest.fn().mockReturnValue('2020-04-15')
expect(component).toMatchSnapshot()
I still get the same old toLocalDateString implementation in the snapshot, my mockReturnValue is not taken into account.
I may be a bit late but hope it helps someone
let mockDate;
beforeAll(() => {
mockDate = jest.spyOn(Date.prototype, 'toLocaleTimeString').mockReturnValue('2020-04-15');
});
afterAll(() => {
mockDate.mockRestore();
});
Can you wrap
new Date(createDate).toLocaleDateString()
in a function, pass it as prop to component and then mock it?
Would the below code work well for you? I am mocking the date object this way.
const realDateToLocaleDateString = Date.prototype.toLocaleDateString.bind(global.Date);
const toLocaleDateStringStub = jest.fn(() => '2020-04-15');
global.Date.prototype.toLocaleDateString = toLocaleDateStringStub;
const date = new Date();
console.log(date.toLocaleDateString()); // returns 2020-04-15
global.Date.prototype.toLocaleDateString = realDateToLocaleDateString;

How can I mock FileReader with jest?

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.

Node.js - Trying to understand singleton

I have a problem with implementing Singleton pattern in my project, which runs on NodeJS (version 8 as far as I know). I created a Logger class, which I want to use whenever we want to log something to a console or database. Originally I implemented this as a Class like this:
class Logger {
constructor() {
}
log() {
}
logToDatabase() {
}
}
module.exports = new Logger()
And I was able to call const logger = require('./Logger') from other classes and use logger.log() function. I then did some research and found a post on StackOverflow where someone recommended to implement the module like this:
module.exports = {
log: () => {
},
logToDatabase: () => {
}
}
So I changed my code and everything (almost) is still working fine. I encountered the problem when I wanted to call logger.log() from another .js file (within their module.exports functions), like so:
another_file.js
const logger = require('./Logger')
module.exports = {
foo: () => {
logger.log()
}
}
The error was logger.log() is not a function.
I managed to solve it by doing something like this:
var logger
module.exports = {
init: () => {
logger = require('./Logger')
},
foo: () => {
logger.log()
}
}
And I made sure that I call init function before I do any calls to foo in my main index.js file.
This seems to work fine now and I don't get any errors, but I want to make sure that I'm doing it the valid way, not by using some cheap hack that shouldn't work but appears to be fine. I would also like to understand why I encounter this issue (I imagine this may be caused by my lack of understanding how module.exports or require actually works. Am I right thinking this can be caused by using different modules? I got a "warning" in code saying we're using CommonJS, and it can be converted to ES6. I've heard about Babel which allows you to convert a code from one module to another (if I understand correctly), although this project ends in a week time and I would want to avoid using new things as much as possible.
Thanks for any help!

Components using Date objects produce different snapshots in different timezones

I'm using Enzyme with enzyme-to-json to do Jest snapshot testing of my React components. I'm testing shallow snapshots of a DateRange component that renders a display field with the current range (e.g. 5/20/2016 - 7/18/2016) and two DateInput components that allow selecting a Date value. This means that my snapshot contains the Dates I pass to the component both in the DateInput props and in a text representation it resolves itself. In my test I'm creating some fixed dates using new Date(1995, 4, 23).
When I run my test in different timezones, this produces different snapshots, because the Date(year, month, ...) constructor creates the date in the local timezone. E.g. use of new Date() produces this difference in snapshot between runs in my local timezone and on our CI server.
- value={1995-05-22T22:00:00.000Z}
+ value={1995-05-23T00:00:00.000Z}
I tried removing the timezone offset from the dates, but then the snapshot differed in the display field value, where the local timezone-dependent representation is used.
- value={5/20/2016 - 7/18/2016}
+ value={5/19/2016 - 7/17/2016}
How can I make my tests produce the same Dates in snapshots regardless of the timezone they're run in?
I struggled with this for hours/days and only this worked for me:
1) In your test:
Date.now = jest.fn(() => new Date(Date.UTC(2017, 7, 9, 8)).valueOf())
2) Then change the TZ env var before running your tests.
So the script in my package.json:
(Mac & Linux only)
"test": "TZ=America/New_York react-scripts test --env=jsdom",
(Windows)
"test": "set TZ=America/New_York && react-scripts test --env=jsdom",
I ended up with a solution comprised of two parts.
Never create Date objects in tests in timezone-dependent manner. If you don't want to use timestamps directly to have readable test code, use Date.UTC, e.g.
new Date(Date.UTC(1995, 4, 23))
Mock the date formatter used to turn Dates into display values, so that it returns a timezone-independent representation, e.g. use Date::toISOString(). Fortunately this was easy in my case, as I just needed to mock the formatDate function in my localization module. It might be harder if the component is somehow turning Dates into strings on its own.
Before I arrived at the above solution, I tried to somehow change how the snapshots are created. It was ugly, because enzyme-to-json saves a local copy of toISOString(), so I had to use _.cloneDeepWith and modify all the Dates. It didn't work out for me anyway, because my tests also contained cases of Date creation from timestamps (the component is quite a bit more complicated than I described above) and interactions between those and the dates I was creating in the tests explicitly. So I first had to make sure all my date definitions were referring to the same timezone and the rest followed.
Update (11/3/2017): When I checked enzyme-to-json recently, I haven't been able to find the local saving of toISOString(), so maybe that's no longer an issue and it could be mocked. I haven't been able to find it in history either though, so maybe I just incorrectly noted which library did it. Test at your own peril :)
I did this by using timezone-mock, it internally replaces the global Date object and it's the easiest solution I could find.
The package supports a few test timezones.
import timezoneMock from 'timezone-mock';
describe('when in PT timezone', () => {
beforeAll(() => {
timezoneMock.register('US/Pacific');
});
afterAll(() => {
timezoneMock.unregister();
});
// ...
https://www.npmjs.com/package/timezone-mock
I ended up getting around this by mocking the toLocaleString (or whatever toString method you are using) prototype. Using sinon I did:
var toLocaleString;
beforeAll(() => {
toLocaleString = sinon.stub(Date.prototype, 'toLocaleString', () => 'fake time')
})
afterAll(() => {
toLocaleString.restore()
})
This way if you are generating strings straight from a Date object, you're still OK.
2020 solution that works for me
beforeEach(() => {
jest.useFakeTimers('modern');
jest.setSystemTime(Date.parse(FIXED_SYSTEM_TIME));
});
afterEach(() => {
jest.useRealTimers();
});
If you're using new Date() constructor instead of Date.now you can do like below:
const RealDate = Date;
beforeEach(() => {
// #ts-ignore
global.Date = class extends RealDate {
constructor() {
super();
return new RealDate("2016");
}
};
})
afterEach(() => {
global.Date = RealDate;
});
This issue is a must visit if you're here.
Adding TZ=UTC to my .env file solved the issue for me.
A simple fact can make it easy.
Just use :
new Date('some string').
This will always give an invalid date and no matter which machine, it will always be invalid date.
cheers.
Try passing a random date like new Date(1466424490000), wherever you call new Date()

Categories

Resources