Skip to content

Instantly share code, notes, and snippets.

@tkrotoff
Last active January 29, 2021 22:19
Show Gist options
  • Save tkrotoff/9fa38d994ec023095084883162d7e313 to your computer and use it in GitHub Desktop.
Save tkrotoff/9fa38d994ec023095084883162d7e313 to your computer and use it in GitHub Desktop.
How I structure my tests

File structure

  • src/fooBar.js
  • src/fooBar.html
  • src/fooBar.scss
  • src/fooBar....
  • src/fooBar.test.js => npm run test
  • src/fooBar.test.e2e.js (if I have E2E tests - Puppeteer, Playwright...) => npm run test:e2e

Tests should not be separated from the source code (think autonomous modules).

package.json

{
  "scripts": {
    "test": "NODE_ENV=test jest --verbose",
    "test:coverage": "jest --coverage",
    "test:e2e": "NODE_ENV=test jest --config jest-e2e.config.js"
  }
}

test()

As for the tests themselves, I follow the functions defined in the original source code (in the same order):

// fooBar.ts

export function fooBar1() {
  ...
}

export function fooBar2() {
  ...
}
// fooBar.test.ts

import { fooBar1, fooBar2 } from './fooBar';

test('fooBar1()', () => {
  ...
});

test('fooBar2()', () => {
  ...
});

describe()

If I have a lot of variations and if appropriate I use describe() to re-group them:

// fooBar.test.ts

import { foorBar1, fooBar2 } from './fooBar';

describe('fooBar1()', () => {
  test('case 1', () => {
    ...
  });

  test('case 2', () => {
    ...
  });
});

test('fooBar2()', () => {
  ...
});

I try to avoid describe(): most of the time they are unnecessary. Same for words like should, when, then... => just noise that brings no value

React components

// MyComponent.test.tsx
import React from 'react';

import { MyComponent } from './MyComponent';

test('render', () => {
  ...
});

test('render without query param', () => {
  ...
});

test('render with query param', () => {
  ...
});

test('fetch error', () => {
  ...
});

(With React class components, I was writing test('render()', () => { ... }) since there was actually a render() method; not anymore with hooks)

Examples:

npm run test:coverage

I ensure that fooBar.test.ts fully covers fooBar.ts by running npm run test:coverage fooBar.test.ts. If some stuffs are impossible to test, I use // istanbul ignore next to get 100% code coverage. Example: https://github.com/pmu-tech/stub-server/blob/v0.3.4/src/stubServer.ts#L84

Most of the time, fooBar.test.ts is way bigger than fooBar.ts, example:

This is because I test not only the nominal case but the corner cases too. My principle is "what ain't tested ain't working".

npm run test

npm run test performs jest --verbose, this way I ensures my tests descriptions are OK:

$ npm run test

PASS src/createHttpError.test.ts
  ✓ new Response() (4 ms)
  ✓ new Response('body') (2 ms)
  ✓ Response.error() (3 ms)
  ✓ 200 OK (12 ms)
  ✓ 204 No Content (7 ms)
  ✓ 404 Not Found (8 ms)
  ✓ no statusText (22 ms)
  ✓ no status (22 ms)
  ✓ no params (2 ms)

PASS src/createResponsePromise.test.ts
  ✓ default Response object (5 ms)
  body methods
    ✓ .arrayBuffer() (5 ms)
    ✓ .blob() (1 ms)
    ✓ .formData() (2 ms)
    ✓ .json() with JSON response (1 ms)
    ✓ .json() with text response (5 ms)
    ✓ .json() with empty response (1 ms)
    ✓ .json() with no response (1 ms)
    ✓ .text()
    ✓ multiple body calls using helpers (11 ms)
    ✓ multiple body calls using regular response (2 ms)
    ✓ multiple body calls using helper + regular response (1 ms)
  get()
    ✓ OK .text() (4 ms)
    ✓ OK .json() (2 ms)
    ✓ fail (16 ms)
    get().text()
      ✓ OK (11 ms)
      ✓ fail (1 ms)
    get().json()
      ✓ OK (1 ms)
      ✓ fail (1 ms)
  post()
    ✓ OK .text() (1 ms)
    ✓ OK .json() (1 ms)
    ✓ fail (1 ms)
    post().text()
      ✓ OK (1 ms)
      ✓ fail (1 ms)
    post().json()
      ✓ OK (4 ms)
      ✓ fail (1 ms)
  flushPromises()
    ✓ createResponsePromise() wrapped inside a function should not fail with flushPromises() (1 ms)
    ○ skipped createResponsePromise() not wrapped inside a function should fail with flushPromises()

PASS src/HttpError.test.ts (6.383 s)
  ✓ HttpError with statusText (HTTP/1.1) (5121 ms)
  ✓ HttpError without statusText because of HTTP/2 (15 ms)

PASS src/createTestServer.test.ts (21.426 s)
  ✓ respond to HTTP requests (5116 ms)
  ✓ respond to HTTPS requests (5032 ms)
  ✓ CORS fail (5019 ms)
  ✓ silence Fastify errors (5019 ms)
  ○ skipped respond to HTTP/2 requests
  ○ skipped should show Jest errors from expect() inside handlers

PASS src/Http.test.ts (152.183 s)
  ✓ defaults.init (57 ms)
  ✓ get() (5027 ms)
  ✓ multiple fetch() (5026 ms)
  ✓ multiple requests (5035 ms)
  ✓ post() (5025 ms)
  ✓ postJSON() (5023 ms)
  ✓ postJSON() should override content-type but keep other headers (5027 ms)
  ✓ postJSON() with undefined request body (5027 ms)
  ✓ postJSON() with null request body (5024 ms)
  ✓ put() (5024 ms)
  ✓ putJSON() (5024 ms)
  ✓ patch() (5022 ms)
  ✓ patchJSON() (5024 ms)
  ✓ del() (5024 ms)
  ✓ cannot connect (53 ms)
  ✓ should not throw under EdgeHTML (5016 ms)
  custom headers
    ✓ get() with JSON headers (5063 ms)
    ✓ get() with Headers instance (5022 ms)
    ✓ postJSON() (5023 ms)
  body methods
    ✓ without body method (5024 ms)
    ✓ .arrayBuffer() (5032 ms)
    ✓ .blob() (5024 ms)
    ✓ .formData() (5023 ms)
    ✓ .json() with JSON reply (5023 ms)
    ✓ .json() with text reply (5021 ms)
    ✓ .json() with empty reply (5025 ms)
    ✓ .json() without content-type reply (5020 ms)
    ✓ .text() (5019 ms)
    ✓ should not override existing accept header (5024 ms)
    ✓ multiple body calls using helpers (5022 ms)
    ✓ multiple body calls using regular response (5021 ms)
    ✓ multiple body calls using helper + regular response (5026 ms)

Test Suites: 5 passed, 5 total
Tests:       3 skipped, 74 passed, 77 total
$ npm run test

PASS src/utils/ErrorBoundary.test.tsx
  ✓ render children if no error (39 ms)
  if an error occured
    ✓ render message + report button (37 ms)
    ✓ user clicks on report button (14 ms)
  withErrorBoundary()
    ✓ displayName (2 ms)
    ✓ render children if no error (4 ms)
    ✓ render a message if an error occured (14 ms)

PASS src/HeroesPagination.test.tsx
  ✓ render without page query param then change page (386 ms)
  ✓ render given a page query param (131 ms)

PASS src/Heroes.test.tsx
  ✓ render (277 ms)
  ✓ render "No results found :(" (7 ms)
  ✓ fetchCharacters() error (31 ms)

PASS src/api/Marvel.test.ts
  ✓ getQueryParams() (2 ms)
  fetch*()
    ✓ fetchCharacters() success (1 ms)
    ✓ fetchCharacters() error (5 ms)
    ✓ fetchCharacter() success (1 ms)
    ✓ fetchCharacter() error (1 ms)

PASS src/Hero.test.tsx
  ✓ render (55 ms)
  ✓ fetchCharacter() error (21 ms)

PASS src/Router.test.tsx
  ✓ HeroesPagination route (33 ms)
  ✓ Hero route (6 ms)
  ✓ PageNotFound route (3 ms)

PASS src/utils/useErrorBoundary.test.tsx
  ✓ useErrorBoundary() (20 ms)

PASS src/utils/getPackageNameFromPath.test.ts
  ✓ getPackageNameFromPath() (3 ms)

PASS src/utils/assert.test.ts
  ✓ TypeScript "asserts condition"
  ✓ console output (2 ms)

PASS src/utils/fakeFetchResponse.test.ts
  ✓ fakeFetchResponseSuccess() (2 ms)
  ✓ fakeFetchResponseError()

PASS src/PageNotFound.test.tsx
  ✓ render (6 ms)

PASS src/Layout.test.tsx
  ✓ render (4 ms)

Test Suites: 12 passed, 12 total
Tests:       29 passed, 29 total
$ npm run test

PASS src/stubServer.test.ts
  ✓ delay (21 ms)
  ✓ unknown route (3 ms)
  files
    ✓ file without HTTP status (21 ms)
    ✓ json file does not exist
    ✓ png file does not exist
    ✓ json (5 ms)
    ✓ png (4 ms)
    ✓ ts (10 ms)
    ✓ js (9 ms)
    ✓ html (4 ms)
  HTTP status codes
    ✓ invalid HTTP status code (12 ms)
    ✓ 400 Bad Request (4 ms)
    ✓ 500 Internal Server Error (4 ms)
    ✓ 204 No Content (4 ms)
  HTTP verbs
    ✓ unknown HTTP verb
    ✓ GET (3 ms)
    ✓ compatibility with lower case (4 ms)
    ✓ POST (4 ms)
    ✓ PUT (5 ms)
    ✓ PATCH (3 ms)
    ✓ DELETE (3 ms)
    ✓ multiple verbs (14 ms)
  proxy
    mocked, no network request
      ✓ URL redirection with param (4 ms)
      ✓ URL redirection to unknown host (8 ms)
      ✓ POST multipart request (7 ms)
    not mocked, performs real network requests, might fail
      ✓ URL redirection with param (92 ms)
      ✓ URL redirection to unknown host (105 ms)
      ✓ POST multipart request (96 ms)
  express request handler
    ✓ js (16 ms)
    ts
      ✓ res.send() (15 ms)
      ✓ res.send() async (505 ms)
      ✓ res.status() (1 ms)
      ✓ res.end() (3 ms)
      ✓ do nothing
      ✓ without param (3 ms)
  get stub name from function
    ✓ no response property (5 ms)
    ✓ with response property (4 ms)

PASS bin/stub-server.test.ts
  ✓ correct config param (311 ms)
  ✓ correct config and port params (311 ms)
  ✓ network request (133 ms)
  ✓ incorrect config param (125 ms)
  ✓ incorrect port param (128 ms)

Test Suites: 2 passed, 2 total
Tests:       42 passed, 42 total
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment