From d9c986fcf8668c67e7a3e8d9d90d0ca5a1e55e9e Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 25 May 2021 02:21:03 -0700 Subject: [PATCH] Fix test flakiness by using jest timers Summary: Some tests were occasionally flaky, by emulating delays, time variation should no longer influence tests. Reviewed By: passy Differential Revision: D28572946 fbshipit-source-id: f4134a6509a0ec0be2e8f36e5623c4882b5531b8 --- .../src/__tests__/PluginContainer.node.tsx | 21 +++++---- .../src/utils/__tests__/sideEffect.node.tsx | 46 ++++++++++++------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/desktop/app/src/__tests__/PluginContainer.node.tsx b/desktop/app/src/__tests__/PluginContainer.node.tsx index 5a7d44f21..4beaf944c 100644 --- a/desktop/app/src/__tests__/PluginContainer.node.tsx +++ b/desktop/app/src/__tests__/PluginContainer.node.tsx @@ -7,6 +7,8 @@ * @format */ +jest.useFakeTimers(); + import React from 'react'; import produce from 'immer'; import {FlipperPlugin} from '../plugin'; @@ -24,7 +26,6 @@ import { import {selectPlugin} from '../reducers/connections'; import {updateSettings} from '../reducers/settings'; import {switchPlugin} from '../reducers/pluginManager'; -import {sleep} from 'flipper-plugin/src/utils/sleep'; interface PersistedState { count: 1; @@ -521,7 +522,7 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => { ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual(['universe!']); expect(renderer.baseElement).toMatchInlineSnapshot(` @@ -552,7 +553,7 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => { }), ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual(['universe!']); // ...nor does a random other store update that does trigger a plugin container render @@ -575,7 +576,7 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => { }), ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual(['universe!', 'london!']); // and same link does trigger if something else was selected in the mean time @@ -597,7 +598,7 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => { }), ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual(['universe!', 'london!', 'london!']); }); @@ -798,7 +799,7 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => { ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual([theUniverse]); expect(renderer.baseElement).toMatchInlineSnapshot(` @@ -829,7 +830,7 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => { }), ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual([theUniverse]); // ...nor does a random other store update that does trigger a plugin container render @@ -852,7 +853,7 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => { }), ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual([theUniverse, 'london!']); // and same link does trigger if something else was selected in the mean time @@ -874,7 +875,7 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => { }), ); }); - await sleep(10); + jest.runAllTimers(); expect(linksSeen).toEqual([theUniverse, 'london!', 'london!']); }); @@ -976,7 +977,7 @@ test('Sandy plugins support isPluginSupported + selectPlugin', async () => { pluginInstance.selectPlugin(definition.id, 'data'); expect(store.getState().connections.selectedPlugin).toBe(definition.id); expect(pluginInstance.activatedStub).toBeCalledTimes(2); - await sleep(10); + jest.runAllTimers(); expect(renderer.baseElement.querySelector('h1')).toMatchInlineSnapshot(`

Plugin1 diff --git a/desktop/app/src/utils/__tests__/sideEffect.node.tsx b/desktop/app/src/utils/__tests__/sideEffect.node.tsx index b56bd0a69..be596018c 100644 --- a/desktop/app/src/utils/__tests__/sideEffect.node.tsx +++ b/desktop/app/src/utils/__tests__/sideEffect.node.tsx @@ -12,6 +12,8 @@ import {createStore, Store} from 'redux'; import produce from 'immer'; import {sleep} from '../promiseTimeout'; +jest.useFakeTimers(); + const initialState = { counter: {count: 0}, somethingUnrelated: false, @@ -73,13 +75,13 @@ describe('sideeffect', () => { expect(events.length).toBe(0); // arrive as a single effect - await sleep(10); + jest.advanceTimersByTime(10); expect(events).toEqual(['counter: 2']); // no more events arrive after unsubscribe unsubscribe(); store.dispatch({type: 'inc'}); - await sleep(10); + jest.advanceTimersByTime(10); expect(events).toEqual(['counter: 2']); expect(warn).not.toBeCalled(); expect(error).not.toBeCalled(); @@ -99,13 +101,13 @@ describe('sideeffect', () => { expect(events.length).toBe(0); // unrelated event doesn't trigger - await sleep(10); + jest.advanceTimersByTime(10); expect(events.length).toBe(0); // counter increment does store.dispatch({type: 'inc'}); - await sleep(10); + jest.advanceTimersByTime(10); expect(events).toEqual(['counter: 1']); expect(warn).not.toBeCalled(); expect(error).not.toBeCalled(); @@ -125,13 +127,13 @@ describe('sideeffect', () => { expect(events.length).toBe(0); // unrelated event doesn't trigger - await sleep(10); + jest.advanceTimersByTime(10); expect(events.length).toBe(0); // counter increment does store.dispatch({type: 'inc'}); - await sleep(10); + jest.advanceTimersByTime(10); expect(events).toEqual(['counter: 1']); expect(warn).not.toBeCalled(); expect(error).not.toBeCalled(); @@ -151,7 +153,7 @@ describe('sideeffect', () => { store.dispatch({type: 'inc'}); }).not.toThrow(); - await sleep(10); + jest.advanceTimersByTime(10); expect(error.mock.calls).toMatchInlineSnapshot(` Array [ Array [ @@ -178,7 +180,7 @@ describe('sideeffect', () => { ); store.dispatch({type: 'inc'}); - await sleep(200); + jest.advanceTimersByTime(200); expect(done).toBe(true); expect(warn.mock.calls[0][0]).toContain("Side effect 'test' took"); }); @@ -195,30 +197,40 @@ describe('sideeffect', () => { // Fires immediately store.dispatch({type: 'inc'}); - await sleep(100); + jest.advanceTimersByTime(100); expect(events).toEqual(['counter: 1']); // no new tick in the next 100 ms - await sleep(300); + jest.advanceTimersByTime(300); store.dispatch({type: 'inc'}); - await sleep(300); + jest.advanceTimersByTime(300); store.dispatch({type: 'inc'}); expect(events).toEqual(['counter: 1']); - await sleep(1000); + jest.advanceTimersByTime(1000); expect(events).toEqual(['counter: 1', 'counter: 3']); - // long time now effect, it will fire right away again - await sleep(2000); + // long time no effect, it will fire right away again + // N.b. we need call sleep here to create a timeout, as time wouldn't progress otherwise + const p = sleep(2000); + jest.advanceTimersByTime(2000); + await p; // ..but firing an event that doesn't match the selector doesn't reset the timer store.dispatch({type: 'unrelated'}); - await sleep(100); + expect(events).toEqual(['counter: 1', 'counter: 3']); + + jest.advanceTimersByTime(100); store.dispatch({type: 'inc'}); store.dispatch({type: 'inc'}); - await sleep(100); + jest.advanceTimersByTime(100); + + const p2 = sleep(2000); + jest.advanceTimersByTime(2000); + await p2; + expect(events).toEqual(['counter: 1', 'counter: 3', 'counter: 5']); }); @@ -239,7 +251,7 @@ describe('sideeffect', () => { store.dispatch({type: 'inc'}); store.dispatch({type: 'inc'}); // arrive as a single effect - await sleep(10); + jest.advanceTimersByTime(10); expect(events).toEqual(['counter: 2', 'counter: 4']); unsubscribe?.(); });