diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-14 15:02:52 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-14 15:02:52 +0000 |
commit | b39170277a588c6d5c6054fbf1c703cec8b71696 (patch) | |
tree | 91e167b1a2f0fb3806d44a8baf39acc6c57055a9 | |
parent | 4588bbc93a7857eb2d031a4f3e0212c0aa46b846 (diff) | |
download | gitlab-ce-b39170277a588c6d5c6054fbf1c703cec8b71696.tar.gz |
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
-rw-r--r-- | doc/development/testing_guide/frontend_testing.md | 32 | ||||
-rw-r--r-- | spec/frontend/helpers/fake_date.js | 49 | ||||
-rw-r--r-- | spec/frontend/helpers/fake_date_spec.js | 33 |
3 files changed, 114 insertions, 0 deletions
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 42ca65a74f2..83d03097466 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -691,6 +691,38 @@ unit tests. 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. +## 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 TBU diff --git a/spec/frontend/helpers/fake_date.js b/spec/frontend/helpers/fake_date.js new file mode 100644 index 00000000000..8417b1c520a --- /dev/null +++ b/spec/frontend/helpers/fake_date.js @@ -0,0 +1,49 @@ +// 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; +}; diff --git a/spec/frontend/helpers/fake_date_spec.js b/spec/frontend/helpers/fake_date_spec.js new file mode 100644 index 00000000000..8afc8225f9b --- /dev/null +++ b/spec/frontend/helpers/fake_date_spec.js @@ -0,0 +1,33 @@ +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); + }); + }); +}); |