Painless Testing

Painless Testing

Testing UIs has always been a hard job. In the components era, there are new tools available that transform completely the way we approach testing. One of the hottest is Jest, and we'll see how it provides an all-in-one testing solution that saves us time and headache.

414db81dca21f468b4d7acd88172370c?s=128

Michele Bertoli

February 09, 2017
Tweet

Transcript

  1. Painless Testing @MicheleBertoli

  2. Mee-KeH-LeH Front End Engineer at Facebook Author of React Design

    Patterns and Best Practices Follow me @MicheleBertoli
  3. “I'm upgrading a pretty big codebase from Jest 0.8.2 to

    15.1.1. Call me a hero.” — Michele Bertoli, Sep 2016
  4. Testing

  5. “Saddest thing I hear from engineers: we aren't writing tests

    right now because we're focusing on building features” — Dmitrii Abramov, Feb 2017
  6. • Time consuming • Complex to setup and maintain •

    Browser tests are slow • False negatives Testing is hard
  7. • Catch some bugs • Confidence (Move fast™) • Refactoring

    • Documentation Testing is important
  8. “Unpopular opinion: component unit testing is overrated.” — Dan Abramov,

    Jul 2016
  9. “Write tests. Not too many. Mostly integration.” — Guillermo Rauch,

    Dec 2016
  10. Mike Cohn, Nov 2009

  11. Dmitrii Abramov, Dec 2016

  12. jest

  13. • Focus on DX • Works out of the box

    • Complete product • Used by Facebook overview
  14. • Interactive mode • Automatically find tests related to changed

    files • Run tests in parallel processes • Sandboxed test files and automatic state resets FAST
  15. • Create coverage reports • Run your tests within a

    fake DOM implementation • Integrated manual mocking library ALL-IN-ONE
  16. • Error messages are helpful and color coded • Stack

    traces point to the source of problems • Jest runs previously failed tests first ERRORS
  17. • Jest works with any compile-to-JS language • Integrates seamlessly

    with Babel • TypeScript through ts-jest Integrations
  18. • Integrated support for promises and async/await • Can run

    asynchronous code synchronously ASYNC
  19. Setup

  20. npm install --save-dev jest yarn add --dev jest // package.json

    "scripts": { "test": "jest" } Getting Started
  21. yarn add --dev babel-jest babel-preset-es2015 // .babelrc { "presets": ["es2015"]

    } Modern javascript
  22. • automock • bail • collectCoverage • notify • testEnvironment

    • timers And many more. configuration
  23. Writing tests

  24. test('Hello, Jest!', () => { expect(true).toBe(true) })

  25. None
  26. test(name, fn) test.only(name, fn) describe(name, fn) describe.only(name, fn) Globals

  27. afterAll(fn) afterEach(fn) beforeAll(fn) beforeEach(fn) Setup and teardown

  28. .toBe(value) .toEqual(value) .toBeDefined() .toBeInstanceOf(Class) .toHaveLength(number) .toThrow(error) expect

  29. expect.extend({ toBeFoo(received) { return { pass: received === 'foo' }

    } }) test('foo is foo', () => expect('foo').toBeFoo()) Custom matchers
  30. Partial objects test('partial objects', () => { const obj =

    { foo: 'bar', baz: Math.random() } expect(obj).toEqual( expect.objectContaining({ foo: 'bar' }) ) })
  31. asymmetric matchers test('asymmetric matchers', () => { const obj =

    { foo: 'bar', baz: Math.random() } expect(obj).toEqual( expect.objectContaining({ foo: 'bar', baz: expect.any(Number), }) ) })
  32. test('works with promises', () => { return getUserName(4).then( userName =>

    expect(userName).toEqual('Mark') ) }) promises
  33. test('works with async/await', async () => { const userName =

    await getUserName(4) expect(userName).toEqual('Mark') }) Async / await
  34. test('spies', () => { const spy = jest.fn() spy() expect(spy).toHaveBeenCalled()

    }) SPIES
  35. MOCK FUNCTIONS test('mock functions', () => { const mock =

    jest.fn(() => 'foo') expect(mock()).toBe('foo') })
  36. // __mocks__/path.js export default { sep: ':' } // spec

    import path from 'path' jest.mock('path') test('mock path', () => expect(path.sep).toBe(':')) Manual mocks
  37. const spy = jest.fn() const debounced = debounce(spy, 1000) debounced('foo')

    example
  38. test('timer functions', () => { jest.runTimersToTime(999) expect(spy).not.toHaveBeenCalled() jest.runTimersToTime(1) expect(spy).toHaveBeenCalledWith('foo') })

    timer FUNCTIONS
  39. Snapshots

  40. snapshots Capture snapshots of React trees or other serializable values

    to simplify UI testing and to analyze how state changes over time.
  41. example const Button = ({ disabled, text, primary }) =>

    ( <button className={ primary ? 'btn--primary' : 'btn--secondary' } disabled={disabled} >{text}</button> )
  42. enzyme test('enzyme', () => { const wrapper = shallow( <Button

    disabled text="Click me!" primary /> ) expect(wrapper.prop('disabled')).toBe(true) expect(wrapper.hasClass('btn--primary')).toBe(true) expect(wrapper.text()).toBe('Click me!') })
  43. Test renderer test('test-renderer', () => { const tree = renderer.create(

    <Button disabled text="Click me!" primary /> ).toJSON() expect(tree).toMatchSnapshot() })
  44. Human-readable exports[`test test-renderer 1`] = ` <button className="btn--primary" disabled={true}> Click

    me! </button> `;
  45. update @@ -1,5 +1,5 @@ <button - className="btn--primary" + className="button--primary"

    disabled={true}> Click me! </button>
  46. watch Watch Usage › Press a to run all tests.

    › Press o to only run tests related to changed files. › Press u to update failing snapshots. › Press p to filter by a filename regex pattern. › Press q to quit watch mode. › Press Enter to trigger a test run.
  47. enzyme-to-json Convert Enzyme wrappers to a format compatible with Jest

    snapshot testing. adriantoine/enzyme-to-json
  48. example test('enzyme-to-json', () => { const wrapper = shallow( <Button

    disabled text="Click me!" primary /> ) expect(toJson(wrapper)).toMatchSnapshot() })
  49. Coverage

  50. None
  51. webpack

  52. CSS MODULES { "jest": { "moduleNameMapper": { "\\.css$": "identity-obj-proxy" }

    } }
  53. resolve.root { "jest": { "modulePaths": [ "/shared/vendor/modules" ] } }

  54. Migrating to jest

  55. codemods Codemods that simplify migrating JavaScript test files from Mocha,

    Tape and AVA to Jest. skovhus/jest-codemods
  56. Next steps Follow @fbjest Stars are welcome facebook/jest

  57. One more thing

  58. “The best testing strategy is not writing tests.” — Michele

    Bertoli, Dec 2016
  59. snapguidist Snapshot testing for React Styleguidist. MicheleBertoli/snapguidist

  60. Jest packages • jest-haste-map • jest-snapshots And many more.

  61. react-fix-it Automagically generate tests from errors. MicheleBertoli/react-fix-it

  62. Any question?