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

TDD with React, Connect Tech, Atlanta, 2017

vjwilson
September 22, 2017

TDD with React, Connect Tech, Atlanta, 2017

Find all the code here: https://github.com/vjwilson/linkshare

Gists for each step: https://gist.github.com/vjwilson

vjwilson

September 22, 2017
Tweet

More Decks by vjwilson

Other Decks in Programming

Transcript

  1. If you’re following along… If you want to follow along

    with the live coding examples, it's best to have the following already installed before you come to this talk. Node (version 6, use nvm if you need to keep multiple versions Node) npm (comes with Node), or yarn, if you prefer that create-react-app CLI npm install -g create-react-app Your favorite text editor git, if you want to pull the sample repo, and check out the starting code for each part of the talk
  2. “A good presentation follows a story…” — John Papa, The

    Art of Public Speaking and Effective Presentations
  3. 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
  4. 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
  5. 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
  6. How do you know it works, if you don’t have

    something to measure it with?
  7. TDD and Agile Make it work Make it right Make

    it fast • Write a test. • Write code. • Refactor for clarity and speed. { }
  8. 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.
  9. But if these two things are true, why doesn’t every

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

    Helps you live longer, and with better quality of life, down the road.
  11. 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.
  12. Let’s assume writing tests first has those benefits… … how

    do you get started, and how do you keep going?
  13. Anatomy of Any Test Given - setup the environment When

    - act on the System Under Test (SUT) in that environment Then - test what happened
  14. 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.
  15. 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
  16. Start from scratch 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
  17. ReactTestUtils Helper functions to help test React components Uses function

    names like scryRenderedDOMComponentsWithClass() Don’t bother to learn it.
  18. Testing the layout components 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
  19. ReactTestUtils, R.I.P 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
  20. 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.
  21. 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
  22. 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. — https://facebook.github.io/jest/docs/snapshot-testing.html Jest Snapshot Testing
  23. 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() • toMatchSelector()
  24. Improving the setup • Really just the base create-react-app •

    Use this as a base, if you want to try TDD on your own. 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
  25. 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'); });
  26. 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); });
  27. 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(); });
  28. 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} />);
  29. 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); });
  30. 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');
  31. 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); });
  32. Example of functional testing Git tag: 9-CucumberJS-setup-for-functional-testing # add libraries

    npm install --save-dev chromedriver cucumber selenium-webdriver # add support file touch features/support/world.js # add hooks file touch features/step_definitions/hooks.js # add feature file touch features/documentation.feature # add step definitions touch features/step_definitions/browser_steps.js
  33. Testing the Cucumber.js site Feature: Example feature As a user

    of Cucumber.js I want to have documentation on Cucumber So that I can concentrate on building awesome applications Scenario: Reading documentation Given I am on the Cucumber.js GitHub repository When I click on "CLI" Then I should see "Running specific features" documentation.feature
  34. Testing the Cucumber.js site var seleniumWebdriver = require('selenium-webdriver'); var {defineSupportCode}

    = require('cucumber'); defineSupportCode(function({Given, When, Then}) { Given('I am on the Cucumber.js GitHub repository', function() { return this.driver.get('https://github.com/cucumber/ cucumber-js/tree/master'); }); … browser_steps.js (part 1)
  35. Testing the Cucumber.js site … When('I click on {string}', function

    (text) { return this.driver.findElement({linkText: text}).then(function(element) { return element.click(); }); }); Then('I should see {string}', function (text) { var xpath = "//*[contains(text(),'" + text + "')]"; var condition = seleniumWebdriver.until.elementLocated({xpath: xpath}); return this.driver.wait(condition, 5000); }); }); browser_steps.js (part 2)
  36. Testing the Linkshare site Git tag: 10-CucumberJS-tests-for-existing-app # same packages

    # same world.js # same hooks.js # new .feature file # new browser_steps.js
  37. Testing the Linkshare site Feature: List of links As a

    user of Linkshare I want to see a list of links So that I can decide what articles I want to read Scenario: Viewing list of links Given I am on the Linkshare homepage When I look for a list of links Then I should see one or more link elements Scenario: Visiting a given link Given I am on the Linkshare homepage When I look for a list of links And I click the link text in the first link item Then I should visit the site in the link item href … documentation.feature (excerpt)
  38. Testing the Linkshare site … defineSupportCode(function({Given, When, Then}) { var

    links; Given('I am on the Linkshare homepage', function() { return this.driver.get('http://localhost:3000'); }); When('I look for a list of links', function () { return this.driver.findElements({css: '.link-list .link- item'}).then(function(elements) { links = elements; }); }); Then('I should see one or more link elements', function () { assert.equal(links.length, 4); }); browser_steps.js (excerpt)
  39. More about functional testing with JS “Cucumber.js”, https://github.com/cucumber/ cucumber-js “End

    to End testing of React apps with Nightwatch”,
 https://blog.syncano.io/testing-syncano/ “Intern”, https://github.com/theintern/intern (a new framework for managing unit and functional tests, thanks to Jaydeep Parmar, @jaydeep98a, for this reference)