Components using Date objects produce different snapshots in different timezones - javascript

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()

Related

Different outcomes for a single stub

I'm using Sinon for stubbing some data retrieval methods during unit testing. Most of these data methods are async, so the resolves syntax has been handy so far for this. What I'm trying to achieve is to dynamically generate different test data based on Math.random() to cover different branches on my code automatically, without having actually to provide hardcoded sample input data for each case. Still, I've realized that the stub just is actually called once upon initialization and, not the return value of it gets fixed/constant during the execution of the testing process (Mocha based). Is there any way to actually provide different outcomes for a single stub using? I've checked the onCall syntax, but it also provides fixed output, just selectable based on current iteration index, but not actual dynamic output, which could even be args/params based, perhaps.
All ideas are welcome!
Current stubbing using Sinon:
sinon.stub(dynamodb, 'get').resolves(stubGet())
The stub itself:
function stubGet () {
// Choose random repo
const i = Math.round(Math.random() * sampleData.length)
const repo = sampleData[i]
// Should it have "new code/push date"?
const isNew = Math.round(Math.random()) === 1
if (isNew) {
repo.pushed_at = { S: '1970-01-01T00:00:00Z' }
}
console.log('repo', repo)
const item = { Item: repo }
console.log(item)
return item
}
The goal would be to hopefully get the random repo or the isNew value.
Randomness is unpredictable. Test code should be predictable, including test data. Otherwise, your tests could be failed with some random data in someday
We should write multiple test cases, each test case uses fixed, as simple as possible test data to test each branch, scene, etc. of the code. Assert whether the returned value meets your expectations.
You should make the test code, test data predictable. For more info, see Unpredictable Test Data

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

How to Write Unit Tests for Functions That Contain SAPUI5 Core API?

I'm new to qUnit with UI5.
I want to test one function formatter.js
formatDate: function(sTimeStamp) {
if (sTimeStamp) {
var iTimeStamp = Number(sTimeStamp.match(/\d/g).join("")),
oDateTimeFormat = DateFormat.getDateTimeInstance();
return oDateTimeFormat.format(new Date(iTimeStamp));
}
return sTimeStamp;
},
Unit test for it:
function formatDateTestCase(assert, sValue, fExpectedNumber) {
var fDate = formatter.formatDate(sValue);
assert.strictEqual(fDate, fExpectedNumber, "Format Date was correct");
}
QUnit.test("Should return valid date", function (assert) {
formatDateTestCase.call(this, assert, "/Date(1510026665790)/", "Nov 7, 2017, 11:51:05 AM");
});
Obviously, this test case will fail when I change language setting. How to improve it?
I think the main problem here is that formatDate is a function with side effects. Should I improve this function itself? By adding locale in formatDate?
Or should I use DateFormat in my test case? Which will make my test meaningless.
I think you should mock the calls to DateFormat here to be able to independently test your code.
Unit Test Considerations
Strictly speaking the point of a Unit Test is to test your - and only your - Unit. You should not test any dependent API. One could argue about that in general but I would definitely NOT recommend to test SAPUI5 API.
One the other hand I would strongly recommend to test the if statement and the Regex part with invalid params (e.g. undefined) and invalid strings. This will ensure your formatter to always work and to return sth. meaningful if it is an empty string.
Sinon.JS: Mocks, Stubs and Spys
You should stub DateFormat.getDateTimeInstance() in your specific test so that the method returns a predictable value (e.g. think of I18N in DateFormat that would give you different test results in different languages).
To do that SAPUI5 already ships with Sinon.JS (be aware of the version included: SAPUI5 1.44 -> Sinon.JS 1.14). Here is a basic example:
sap.ui.define([
"my/module/formatter",
"sap/ui/core/format/DateFormat",
"sap/ui/thirdparty/sinon",
"sap/ui/thirdparty/sinon-qunit"
], function (formatter, DateFormat) {
QUnit.test("Should return valid date", function (assert) {
// stub the method
sinon.stub(DateFormat, "getDateTimeInstance");
// ensure a predictable outcome
DateFormat.getDateTimeInstance.returns({
format: function(oDate) {
return oDate.getTime();
}
});
var fDate = formatter.formatDate("/Date(1510026665790)/");
assert.strictEqual(fDate, "1510026665790", "Format Date was correct");
// Optional: test if the stubbed function was called
assert.ok(DateFormat.getDateTimeInstance.calledOnce);
// don't forget to restore the stub so that it does not interfere with other tests
DateFormat.getDateTimeInstance.restore();
});
});
By stubbing DateFormat.getDateTimeInstance you stop testing core API and it's outcomes and you can focus on what matters most: YOUR code.
BR Chris

node.js log module bunyan change timezone

I'm using this logging module bunyan.js which is included in the framwork restify.js. The module does outprint a time in the log file/console, however, I want to change the time to UTC/GMT, not sure if it's possible wihtout modifying the module code?
If you don't want to use local time anywhere else in your process, one way to achieve what you want is to change the timezone for the process. Either by writing this statement at the startup of you application:
process.env.TZ = 'UTC'
Or by starting it with a environment variable from the command line, like this:
TZ=UTC node main.js
I was also facing the same issue and resolved it by adding a custom attribute, localtime, while creating the logger using bunyan.createLogger method like this:
var init = function () {
log = bunyan.createLogger({
name: 'myLogs',
src: true,
localtime: new Date().toString();
});
};
By doing this, I get an extra field in my log called localtime with the appropriate time as per my timezone.
Hope this helps.

Categories

Resources