Commit c09a3227 authored by Paul Slaughter's avatar Paul Slaughter Committed by Mark Florian

Add fake_date test helper and use it in spec

https://gitlab.com/gitlab-org/gitlab/-/issues/236004
parent c7d3515e
...@@ -691,6 +691,38 @@ unit tests. ...@@ -691,6 +691,38 @@ unit tests.
Instead of `setImmediate`, use `jest.runAllTimers` or `jest.runOnlyPendingTimers` to run pending timers. Instead of `setImmediate`, use `jest.runAllTimers` or `jest.runOnlyPendingTimers` to run pending timers.
The latter is useful when you have `setInterval` in the code. **Remember:** our Jest configuration uses fake timers. The latter is useful when you have `setInterval` in the code. **Remember:** our Jest configuration uses fake timers.
## Avoid non-deterministic specs
Non-determinism is the breeding ground for flaky and brittle specs. Such specs end up breaking the CI pipeline, interrupting the work flow of other contributors.
1. Make sure your test subject's collaborators (e.g., axios, apollo, lodash helpers) and test environment (e.g., Date) behave consistently across systems and over time.
1. Make sure tests are focused and not doing "extra work" (e.g., needlessly creating the test subject more than once in an individual test)
### Faking `Date` for determinism
Consider using `useFakeDate` to ensure a consistent value is returned with every `new Date()` or `Date.now()`.
```javascript
import { useFakeDate } from 'helpers/fake_date';
describe('cool/component', () => {
useFakeDate();
// ...
});
```
### Faking `Math.random` for determinism
Consider replacing `Math.random` with a fake when the test subject depends on it.
```javascript
beforeEach(() => {
// https://xkcd.com/221/
jest.spyOn(Math, 'random').mockReturnValue(0.4);
});
```
## Factories ## Factories
TBU TBU
......
...@@ -90,7 +90,7 @@ exports[`NetworkPolicyList component renders policies table 1`] = ` ...@@ -90,7 +90,7 @@ exports[`NetworkPolicyList component renders policies table 1`] = `
> >
<div> <div>
2 weeks ago 2 months ago
</div> </div>
</td> </td>
......
...@@ -3,17 +3,15 @@ import { GlTable } from '@gitlab/ui'; ...@@ -3,17 +3,15 @@ import { GlTable } from '@gitlab/ui';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue'; import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue';
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants'; import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import { useFakeDate } from 'helpers/fake_date';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { mockPoliciesResponse } from '../mock_data'; import { mockPoliciesResponse } from '../mock_data';
jest.mock('timeago.js', () => ({
format: jest.fn().mockReturnValue('2 weeks ago'),
register: jest.fn(),
}));
const mockData = mockPoliciesResponse.map(policy => convertObjectPropsToCamelCase(policy)); const mockData = mockPoliciesResponse.map(policy => convertObjectPropsToCamelCase(policy));
describe('NetworkPolicyList component', () => { describe('NetworkPolicyList component', () => {
useFakeDate();
let store; let store;
let wrapper; let wrapper;
......
// Frida Kahlo's birthday (6 = July)
export const DEFAULT_ARGS = [2020, 6, 6];
const RealDate = Date;
const isMocked = val => Boolean(val.mock);
export const createFakeDateClass = ctorDefault => {
const FakeDate = new Proxy(RealDate, {
construct: (target, argArray) => {
const ctorArgs = argArray.length ? argArray : ctorDefault;
return new RealDate(...ctorArgs);
},
apply: (target, thisArg, argArray) => {
const ctorArgs = argArray.length ? argArray : ctorDefault;
return RealDate(...ctorArgs);
},
// We want to overwrite the default 'now', but only if it's not already mocked
get: (target, prop) => {
if (prop === 'now' && !isMocked(target[prop])) {
return () => new RealDate(...ctorDefault).getTime();
}
return target[prop];
},
getPrototypeOf: target => {
return target.prototype;
},
// We need to be able to set props so that `jest.spyOn` will work.
set: (target, prop, value) => {
// eslint-disable-next-line no-param-reassign
target[prop] = value;
return true;
},
});
return FakeDate;
};
export const useFakeDate = (...args) => {
const FakeDate = createFakeDateClass(args.length ? args : DEFAULT_ARGS);
global.Date = FakeDate;
};
export const useRealDate = () => {
global.Date = RealDate;
};
import { createFakeDateClass, DEFAULT_ARGS, useRealDate } from './fake_date';
describe('spec/helpers/fake_date', () => {
describe('createFakeDateClass', () => {
let FakeDate;
beforeAll(() => {
useRealDate();
});
beforeEach(() => {
FakeDate = createFakeDateClass(DEFAULT_ARGS);
});
it('should use default args', () => {
expect(new FakeDate()).toEqual(new Date(...DEFAULT_ARGS));
expect(FakeDate()).toEqual(Date(...DEFAULT_ARGS));
});
it('should have deterministic now()', () => {
expect(FakeDate.now()).not.toBe(Date.now());
expect(FakeDate.now()).toBe(new Date(...DEFAULT_ARGS).getTime());
});
it('should be instanceof Date', () => {
expect(new FakeDate()).toBeInstanceOf(Date);
});
it('should be instanceof self', () => {
expect(new FakeDate()).toBeInstanceOf(FakeDate);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment