Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD with React

TDD with React

Presented at the Charlotte JS Meetup

vjwilson

August 11, 2017
Tweet

More Decks by vjwilson

Other Decks in Programming

Transcript

  1. — John Papa, The Art of Public Speaking and Effective

    Presentations “A good presentation follows a story…”
  2. Things that Increase Code Quality • Hiring competent employees •

    Providing employee training • Empowering employees to make decisions • Code reviews • Continuous Integration (short feedback loops) • Test-Driven Development
  3. Definition of Test-Driven Development • Write a test for the

    next bit of functionality you want to add. • Write the functional code until the test passes. • Refactor both new and old code to make it well structured. - Martin Fowler, “TestDrivenDevelopment”, 
 https://martinfowler.com/bliki/TestDrivenDevelopment.html
  4. 1. Write Tests 2. Run Test (TEST FAILS) 3. Write

    Code to Make the Test to Pass 4. Run Test (TEST PASS) 5. Refactor (MAKE CODE CLEANER AND FASTER) 6. Repeat FAIL PASS REFACTOR
  5. How do you know it works, if you don’t have

    something to measure it with?
  6. TDD and Agile • Make it work • Make it

    right • Make it fast • Write a test. • Write code. • Refactor for clarity and speed. { }
  7. or, Why Should I Bother? Benefits of TDD 1. Writing

    tests first really helps you avoid a lot of bad designs. 
 It makes you think about the code you need to write, BEFORE you write it. 2. Once you have tests, they help you avoid introducing subtle bugs when you have to change the code later. 
 Existing tests help prevent regressions in your code, where adding or changing one thing accidentally breaks another thing.
  8. But if these two things are true, why doesn’t every

    developer write tests first, all the time?
  9. Exercising • Gives you more energy and other immediate health

    benefits. • Helps you live longer, and with better quality of life, down the road.
  10. Saving money • Gives you a better credit rating, and

    an emergency reserve, in the short term. • Allows you to retire earlier, and do more things in retirement, in the future.
  11. Flossing • Helps prevent cavities and gum disease now. •

    Helps you keep your teeth when you get older.
  12. Let’s assume writing tests first has those benefits… … how

    do you get started, and how do you keep going?
  13. create-react-app • Sane build system already in place • Test

    framework for unit and integration tests already in place • You can add libraries to it. • You can eject and substitute parts of the testing framework. • You can “eject” and then port the testing framework to other projects, or replace it with different testing libraries.
  14. Linkshare, a Delicious clone Building an app with TDD •

    Display a list of links • Show how many times each link has been “favorited” • Allow a visitor to “favorite” links • You can fork or clone the repo from
 https://github.com/vjwilson/linkshare • Includes Git tags that are numbered for each step in this tutorial
  15. Start from scratch • Really just the base create-react-app •

    Use this as a base, if you want to try TDD on your own. import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; it('renders without crashing', () => { const div = document.createElement('div'); ReactDOM.render(<App />, div); }); Git tag: 1-start-with-create-react-app
  16. ReactTestUtils • Helper functions to help test React components •

    Names like scryRenderedDOMComponentsWithClass() • Don’t bother to learn it. Airbnb has released a testing utility called Enzyme, which makes it easy to assert, manipulate, and traverse your React Components' output. If you're deciding on a unit testing utility to use together with Jest, or any other test runner, it's worth checking out: http://airbnb.io/enzyme/ -from Facebook’s own documentation
  17. Testing the layout components • Historical significance only (don’t do

    this at home, kids) it('renders a header with the appropriate class', () => { const wrapper = ReactTestUtils.renderIntoDocument(<App />); const header = ReactTestUtils.scryRenderedDOMComponentsWithTag(wrapper, 'header'); const headerWithClass = ReactTestUtils.scryRenderedDOMComponentsWithClass(wrapper, 'app-header'); expect(header.length).toEqual(1); expect(headerWithClass.length).toEqual(1); expect(header).toEqual(headerWithClass); }); Git tag: 2-show-testing-with-test-utils
  18. Enzyme • Wraps ReactTestUtils in idiomatic selectors • Looks like

    jQuery, with functions like find(), children(), parent() etc. • Three functions for rendering: - Shallow stubs child components, good for unit tests - Mount renders all a components descendants, good for integration testing - Static renders to the final HTML that React would render.
  19. Jest matchers • Based on the popular Expect style of

    test matchers • .toBeTruthy() • .toBeFalsy() • .toBeGreaterThan(number) • .toBeGreaterThanOrEqual(number) • .toBeLessThan(number) • .toBeLessThanOrEqual(number) • .toContain(item) • .toEqual(value)
 • .toHaveLength(number) • .toMatch(regexpOrString) • .toMatchObject(object) • .toHaveProperty(keyPath, value) • .toHaveBeenCalled() • .toHaveBeenCalledTimes(number) • .toHaveBeenCalledWith(arg1, …) • … and many more
  20. — https://facebook.github.io/jest/docs/snapshot-testing.html Is it possible to apply test-driven development principles

    with snapshot testing?
 Although it is possible to write snapshot files manually, that is usually not approachable. Snapshots help figuring out whether the output of the modules covered by tests is changed, rather than giving guidance to design the code in the first place. What About Snapshot Testing?
  21. Jest-Enzyme Matchers • Makes Enzyme’s Chai-style functions more Expect-like •

    toBeChecked() • toBeDisabled() • toBeEmpty() • toBePresent() • toContainReact() • toHaveClassName() • toHaveHTML() • toHaveProp() • toHaveRef() • toHaveState() • toHaveStyle() • toHaveTagName() • toHaveText() • toIncludeText() • toHaveValue() • toMatchElement()
  22. Improving the setup it('renders a header with the appropriate class',

    () => { const wrapper = shallow(<App />); const header = wrapper.find('header'); expect(header).toHaveClassName('app-header'); }); Git tag: 3-starting-structure-for-easier-testing
  23. Building the first component with TDD Git tag: 4-link-item-component it('should

    render its favorites prop', () => { const wrapper = shallow(<LinkItem favoriteCount="12" />); const favorites = wrapper.find('.link-item-faves'); expect(favorites.text()).toEqual('12'); }); it('should render its link prop', () => { const wrapper = shallow(<LinkItem linkUrl="http:// www.daringfireball.com" />); const linkInfo = wrapper.find('.link-item-info'); expect(linkInfo.text()).toEqual('http:// www.daringfireball.com'); });
  24. Testing components within components Git tag: 5-link-list-component it('should render an

    array of LinkItems', () => { const links = [ { linkUrl: 'http://www.csszengarden.com', favorites: 10 }, { linkUrl: 'http://www.daringfireball.com', favorites: 15 } ]; const wrapper = shallow(<LinkList links={links} />); expect(wrapper.find(LinkItem)).toHaveLength(links.length); });
  25. Integration tests Git tag: 6-incrementing-favorites-count it('should call the action prop

    when the Add button is clicked', () => { const mock = jest.fn(); const props = { favoriteCount: 11, linkUrl: 'https://alistapart.com', action: mock } const wrapper = shallow(<LinkItem {...props} />); const addFaveButton = wrapper.find('.add-fave'); addFaveButton.simulate('click'); expect(mock).toHaveBeenCalled(); });
  26. Integration tests, continued… (part 2) Git tag: 6-incrementing-favorites-count it('should increment

    the specified favorite count', () => { const links = [ { linkUrl: 'http://www.csszengarden.com', favorites: 10 }, { linkUrl: 'https://daringfireball.net', favorites: 15 } ]; const linkToTest = 1; const secondLinkCount = links[linkToTest].favorites; const props = { links: links }; const wrapper = mount(<App {...props} />);
  27. Integration tests, continued… (part 3) Git tag: 6-incrementing-favorites-count // it('should

    increment the specified favorite count’ … const secondAddButton = wrapper.find(LinkItem).at(linkToTest); secondAddButton.find('.add-fave').simulate('click'); const updatedLinks = wrapper.state('links'); const secondLinkInState = updatedLinks[linkToTest]; expect(secondLinkInState.favorites).toEqual(secondLinkCount + 1); });
  28. Redux with TDD Git tag: 7-Add-Redux-with-testing it('should increment the favorite

    count for a link', () => { const stateBefore = [ { linkUrl: 'http://www.csszengarden.com', favorites: 10 }, { linkUrl: 'https://daringfireball.net', favorites: 15 } ]; const action = linkActions.incrementFavorites('http:// www.csszengarden.com');
  29. Redux with TDD, continued… (part 2) Git tag: 7-Add-Redux-with-testing //

    it('should increment the favorite count for a link' … const stateAfter = [ { linkUrl: 'http://www.csszengarden.com', favorites: 11 }, { linkUrl: 'https://daringfireball.net', favorites: 15 } ]; const result = linkReducer(stateBefore, action); expect(result).toEqual(stateAfter); });
  30. Example of functional testing Git tag: 8-Functional-testing 'Page has appropriate

    title': function(client) { var linkListPage = client.page.linkListPage(); linkListPage .navigate() .waitForElementPresent('@linkList'); linkListPage.expect.element('@pageTitle').text.to.contain('Welco me to Linkshare'); linkListPage.expect.element('@linkList').to.be.visible; client.elements('css selector','.link-list .link-item', function (result) { client.assert.equal(result.value.length, 4); }); client.end();
  31. More about functional testing with JS • “End to End

    testing of React apps with Nightwatch”,
 https://blog.syncano.io/testing-syncano/ • “Cucumber.js”, https://github.com/cucumber/cucumber-js