Last active
June 19, 2019 09:03
-
-
Save isthatcentered/1877e6019599220b42c754a469f01542 to your computer and use it in GitHub Desktop.
React - Testing setup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
yarn add testdouble @testing-library/react jest-dom testdouble-jest jest-then enzyme enzyme-adapter-react-16 @types/enzyme @types/enzyme-adapter-react-16 -D |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MOCKING LOCALSTORAGE | |
Object.defineProperty( window, "localStorage", { | |
value: { | |
clear: jest.fn() as any, | |
getItem: jest.fn() as any, | |
setItem: jest.fn() as any, | |
removeItem: jest.fn() as any, | |
} as Storage, | |
} ) | |
// MOCKING FETCH | |
describe( `Bootstrap()`, () => { | |
const configFromFBServer = "config_from_firebase_server" | |
beforeEach( () => resolveFetchCallWith( configFromFBServer ) ) | |
test( `Initializes firebase with server config`, async () => { | |
await new Firebase().bootstrap() | |
expect( fetch ).toHaveBeenCalledWith( "/__/firebase/init.json" ) | |
expect( firebase.initializeApp ).toHaveBeenCalledWith( configFromFBServer ) | |
} ) | |
} ) | |
export function resolveFetchCallWith( data: any ) | |
{ | |
jest.spyOn( window, "fetch" ) | |
.mockResolvedValue( makeFetchResponse( data ) ) | |
} | |
export function makeFetchResponse( data: any ): Response | |
{ | |
return { | |
json: () => Promise.resolve( data ), | |
} as Response | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "jest-then" | |
import td from "testdouble" // https://github.com/testdouble/testdouble.js | |
import { cleanup } from "@testing-library/react" // https://testing-library.com/docs/ | |
import "jest-dom/extend-expect" // https://www.npmjs.com/package/jest-dom | |
import Enzyme from 'enzyme' | |
import Adapter from 'enzyme-adapter-react-16'// https://airbnb.io/enzyme/ | |
// @ts-ignore | |
import testDoubleAdapter from "testdouble-jest" // https://github.com/testdouble/testdouble-jest | |
Enzyme.configure({ adapter: new Adapter() }); | |
testDoubleAdapter( td, jest ) | |
afterEach( () => { | |
cleanup() // react-testing-library-setup (will have to check performance impact without it as global) | |
td.reset() | |
jest.resetAllMocks() | |
} ) | |
declare global | |
{ | |
interface Array<T> | |
{ | |
last(): T | undefined | |
first(): T | undefined | |
hasDuplicates: () => boolean | |
} | |
} | |
Object.defineProperties( Array.prototype, { | |
hasDuplicates: { | |
value: function () { | |
const withoutDuplicates = new Set( this ) | |
return withoutDuplicates.size !== this.length | |
}, | |
}, | |
last: { | |
value: function () { | |
return this[ this.length - 1 ] | |
}, | |
}, | |
first: { | |
value: function () { | |
return this[ 0 ] | |
}, | |
}, | |
} ) | |
export default undefined // otherwise ts will throw "Cannot compile namespaces when the '--isolatedModules' flag is provided." See note in https://facebook.github.io/create-react-app/docs/running-tests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { fireEvent, render } from "react-testing-library" | |
import * as React from "react" | |
import { ReactElement } from "react" | |
import { createHistory, createMemorySource, LocationProvider } from "@reach/router" | |
import { App } from "./App" | |
import { ServicesContainer, ServicesContext } from "./ServicesContext" | |
import { object } from "testdouble" | |
export function Fake<T>( name: string ): T | |
{ | |
return name as any as T | |
} | |
export function aside<T>( cb: ( res: any ) => void ): ( res: T ) => T | |
{ | |
return ( res ) => { | |
cb( res ) | |
return res | |
} | |
} | |
export function tick(): Promise<undefined> | |
{ | |
return new Promise( resolve => | |
process.nextTick( () => resolve() ) ) | |
} | |
export function customRender( component: ReactElement<any>, services: Partial<ServicesContainer> = object<ServicesContainer>() ) | |
{ | |
const utils = render( | |
<ServicesContext.Provider value={services as ServicesContainer}> | |
{component} | |
</ServicesContext.Provider>, | |
) | |
const change = ( label: RegExp, value: any ) => | |
fireEvent.change( utils.getByLabelText( label ), { target: { value } } ) | |
const fill = ( label: RegExp, value: string ) => change( label, value ) | |
const slide = ( label: RegExp, value: number ) => change( label, value ) | |
const click = ( label: RegExp ) => | |
fireEvent.click( utils.getByText( label ) ) | |
const submit = ( label: RegExp ) => | |
fireEvent.submit( (utils.getByText( label ) as HTMLElement).closest( "form" )! ) | |
return { | |
...utils, | |
wrapper: utils.container.firstChild as HTMLElement, | |
change, | |
fill, | |
slide, | |
click, | |
submit, | |
} | |
} | |
export function appRender( route: string, services: Partial<ServicesContainer> ) | |
{ | |
const [ path, query ] = route.split( "?" ) | |
const history = createHistory( createMemorySource( route ) ) | |
history.location.search = query ? | |
`?${query}` : | |
"" | |
const wrapper = customRender( | |
<LocationProvider history={history}> | |
<App/> | |
</LocationProvider>, services ) | |
return { | |
...wrapper, | |
navigate: history.navigate, | |
history, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment