...
 
Commits (3)
import React from 'react';
import ReactDOM from 'react-dom';
import {mount} from 'enzyme';
import React from "react";
import ReactDOM from "react-dom";
import { mount } from "enzyme";
import MyComponent from './index';
import MyComponent from "./index";
const oldNode = <h1>My old node</h1>;
......@@ -11,99 +11,41 @@ const oldNode = <h1>My old node</h1>;
// First, we should write a test to confirm that the default
// state for the component is correctly set.
test('it has a default state', () => {
test("it has a default state", () => {
const component = mount(<MyComponent />);
const defaultState = {
num: 0,
set: [1, 2, 3]
};
// What is the default state for this component?
// Looking at the constructor method may give some hints.
// Set the below object to match the default state of the component.
const defaultState = {};
// We'll set the assertion variable to our component's state.
// If we use component.props() to access the component's props,
// how would you guess accessing state would work?
const assertion = null;
const assertion = component.state();
expect(assertion).toEqual(defaultState);
});
test('it renders myThing prop', () => {
// The component is mounted, with a prop myThing passed to it.
test("it renders myThing prop", () => {
const component = mount(<MyComponent myThing="foobar" />);
// Look at the code for MyComponent. Where is myThing used in the
// render function? Can you identify what kind of "node" it is
// used in? Can you recreate that node below?
//
// If you're unsure what a "node" is, take a look at the
// oldNode variable above, which is an h1 node. Notice that it
// also contains text inside of it as well. Can you think of
// how myThing would look next to oldNode?
const node = null;
// Enzyme will search for the node within the component, confirming
// that it does exist inside the component.
const node = <span>My thing is foobar</span>;
expect(component.contains(node)).toEqual(true);
});
test('can find the num', () => {
test("can find the num", () => {
const component = mount(<MyComponent />);
// We need to find the 'num' state variable inside of our component.
// There's a couple ways to do this, but let's try looking through
// the rendered output of the component.
// Try setting renderedComponentText to the result of calling ".text()"
// on the component.
const renderedComponentText = null;
// Using renderedComponentText, which is a text-rendered version
// of the component, we want to check that the string output
// "The num is X" appears, where X is the default value of num.
//
// Try setting assertion to the output of
// renderedComponentText.includes(STRING), where STRING is
// the "string output" referred to above.
const assertion = null;
const renderedComponentText = component.text();
const assertion = renderedComponentText.includes("The num is 0");
expect(assertion).toEqual(true);
});
test('can find the list', () => {
test("can find the list", () => {
const component = mount(<MyComponent />);
// In this test, we want to find the "ul" list
// element for this component. Try using the
// component.find function, passing in 'ul' as the
// "node" we're searching for.
const list = null;
// We want to check the number of elements inside of our list.
// To do this, we should get the "children" of our list node,
// and call .length on it. If the children of a node are accessible
// via the ".children" function, and you can get the length of
// an array using ".length", how would you set assertion to the
// number of children elements of the list variable?
const assertion = null;
const list = component.find("ul");
const assertion = list.children().length;
expect(assertion).toEqual(3);
});
test('clicking the button increments num', () => {
test("clicking the button increments num", () => {
const component = mount(<MyComponent />);
expect(component.state().num).toEqual(0);
// Similarly to the last example, finding the button
// component is as easy as calling ".find" on the component,
// passing the string 'button' as the node we're looking for.
// Try that below!
const button = null;
// To "click" the button, we need to use the ".simulate" function.
// Call ".simulate" on button, passing the 'click' string to
// indicate the action we want to perform.
// Finally, we should check that the "num" value in state has
// updated. Assuming that the second line of this test
// where we ensure num is 0 is correct, what would num now be?
// Test that below!
const button = component.find("button");
button.simulate("click");
expect(component.state().num).toEqual(1);
});
......@@ -6,193 +6,30 @@ import { fireEvent, render } from "react-testing-library";
import MyComponent from "./index";
// In this exercise, we'll progressively move each
// enzyme-based test from this previous exercise over to using
// react-testing-library. react-testing-library is designed
// with a number of improvements in design/functionality over enzyme,
// with the primary decision being to remove shallow rendering
// entirely, and focus on true testing of components and behavior,
// using *real* DOM elements and *real* event handling.
//
// Let's begin with a simple example.
test("it renders myThing prop", () => {
const myThing = "foobar";
// In our current enzyme example, we're using shallow rendering
// to render a single layer of our component tree - in this case,
// just MyComponent. Because our component just renders a single
// layer, we're only using shallow for the API, not necessarily
// for the "shallow" functionality itself.
//
// Let's switch over to using react-testing-library. The lib
// exposes a few functions, the primary of being `render`.
// Call the render function, passing in the rendered component
// in the same way that it has been called for shallow (you can
// even just copy paste the shallow function call, and replace
// shallow with render.
//
// Now for the interesting part. react-testing-library returns an
// object from render - the object is intended to be destructured,
// so you can get access to the methods that you want to use for
// testing and assertions.
//
// const { getByText } = render(<TestComponent />)
//
// In this test, we want to test for a text value in the component,
// based on the myThing prop. We know that in MyComponent, the
// myThing prop is used inside of a span that displays the text
// 'My thing is {myThing}'. Use the render function and destructure
// getByText out from the returned object (like in the above example).
// Call the getByText function, passing in the interpolated string
// 'My thing is {myThing}' (look up the ES6 string interpolation
// syntax if you need help!). Set the value of this function call
// to textNode.
//
// Now we have a textNode we can make an assertion with. We
// just want to test that the textNode *exists*, we can simply
// assert using `toBeTruthy`:
//
// expect(1).toBeTruthy()
//
// Make this assertion with textNode.
const component = shallow(<MyComponent myThing={myThing} />);
const node = <span>My thing is {myThing}</span>;
expect(component.contains(node)).toEqual(true);
const { getByText } = render(<MyComponent myThing={myThing} />);
const textNode = getByText("My thing is foobar");
expect(textNode).toBeTruthy();
});
test("can find the num", () => {
// getByText is just one of many available methods returned
// from react-testing-library. Another useful tool is `getByTextId`.
//
// One of the most unreliable methods in enzyme is `find`. Because
// it requires looking up by a specific DOM tag, class, or ID, it's
// prone to failure if you change an expected value: for instance,
// if the test is looking for a span tag, but you've changed it to
// an h4, the test will fail. This is a "brittle" test: one that
// is slightly unreliable.
//
// The author of react-testing-library has a pretty interesting
// solution to this: using the "data-" attribute behavior in HTML
// to set a "test id" on specific DOM elements, which we can use
// to reliably look up specific DOM elements. You can read more
// here (and I recommend reading it! It's one of the best JS blog
// posts I've read in quite some time):
//
// https://blog.kentcdodds.com/making-your-ui-tests-resilient-to-change-d37a6ee37269
//
// Let's try using the getByTestId function in react-testing-library
// to update this "num" test. Right now we're using component.text()
// to get the full text of the component, and using .includes to
// find a substring inside of that text. Call the render function,
// passing in MyComponent, and destructure the function getByTestId
// out of the value returned.
//
// Let's modify this test slightly and set a numNode variable to the
// value of calling getByTestId, passing in the argument "num" as a
// test ID for looking up the correct field. Where does this come from?
// I've gone and updated src/index.js with a few data-test-id attributes
// in the render function! Go take a look at those to see the full workflow
// between your component's render function, and getByTestId inside your tests.
//
// Once you've done this, make a similar assertion to the previous test:
// we expect numNode to *exist*, or have a *truthy* value - how do we assert
// that in Jest + react-testing-library?
const component = shallow(<MyComponent />);
const renderedComponentText = component.text();
const assertion = renderedComponentText.includes("The num is 0");
expect(assertion).toEqual(true);
const { getByTestId } = render(<MyComponent />);
const numNode = getByTestId("num");
expect(numNode).toBeTruthy();
});
test("can find the list", () => {
// Let's replace this test with another usage of
// getByTestId. We want to find the ul element,
// which has a data-test-id attribute of "test".
//
// Using the previous test as a reference, set
// listNode to the value of calling getByTestId
// with our list's test id.
//
// Once we've found listNode, we should assert
// that it has three children, similar to our
// current version of the test. We can do this
// by using the `toHaveLength` check:
//
// expect(array).toHaveLength(5)
//
// If we wanted to check the length of the
// listNode's children (available at
// listNode.children), how would we format that
// assertion?
const component = shallow(<MyComponent />);
const list = component.find("ul");
const assertion = list.children().length;
const { getByTestId } = render(<MyComponent />);
const listNode = getByTestId("list");
const assertion = listNode.children.length;
expect(assertion).toEqual(3);
});
test("clicking the button increments num", () => {
// Finally, we'll use react-testing-library
// to handle interactivity. One issue with enzyme
// is the idea of "simulating" events: as the author
// of react-testing-library says:
//
// "The more your tests resemble the way your software
// is used, the more confidence they can give you."
// - https://twitter.com/kentcdodds/status/977018512689455106
//
// *Simulating* a click is *not* how your software is
// used! The user *actually* clicks the button.
// react-testing-library has tools for interacting with
// buttons, inputs, and any other HTML that the user would
// normally interact with, but importantly, it *actually*
// performs/fires events on those HTML elements, and doesn't
// just simulate them.
//
// Review this test as it is before we move on to rewriting it.
// In this test, we check a value in state, simulate a click, and
// then check the new value in state. The success case for this
// test is that `num` inside of state starts at 0, and then
// increments to 1.
//
// This state value is represented in our UI via a span that
// renders the text "The num is {num}". If we want to _actually_
// test the functionality of our component, and not just the
// implementation, we should validate that text, instead of the
// state object inside of the component.
//
// First, let's check the text at initial render. Call the
// render function, passing in MyComponent, and destructure
// getByText out of the object returned from render.
// Set numNode to the value of calling getByText, looking for
// the text "The num is {num}". Replace "{num}" with the
// *initial value of num in your state*. Assert that this
// numNode is truthy.
//
// Now we want to actually fire an event. react-testing-library
// allows you to import a named function, fireEvent, from inside
// of the package. We want to find the button in our component, and
// pass it into the function fireEvent.click:
//
// fireEvent.click(getByText('My button text'))
//
// Look at the definition of MyComponent, and rewrite (and call)
// the above example code, replacing 'My button text' with the actual
// text for our button.
//
// Now that we've *actually* clicked the button (and not just simulated
// it), we should check the new value of numNode, since the text should
// have changed with our new num value. Set numNode to the value of
// "The num is {num}", once again replacing "{num}" with the **new** value
// of your num in state. Make another assertion that this new version
// of numNode is truthy.
//
// Notice that we're testing the UI here, not the implementation! If
// we want to test the *actual* behavior of our application, and what's
// useful to our users, this means testing the text that is rendered based
// on our num in state, not just the state and num value themselves!
const component = shallow(<MyComponent />);
expect(component.state().num).toEqual(0);
const button = component.find("button");
button.simulate("click");
expect(component.state().num).toEqual(1);
const { getByText } = render(<MyComponent />);
fireEvent.click(getByText("Click me!"));
const numNode = getByText("The num is 1");
expect(numNode).toBeTruthy();
});
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import React from "react";
import ReactDOM from "react-dom";
import { shallow } from "enzyme";
// First, we need to import the component.
// Import `MyFirstComponent` from the `index`
// file - note that it's in the same folder, which
// will be referenced as `./`.
import MyFirstComponent from "./index";
// While we should also test that the functionality
// of the component works, we should first ensure
// it's actually able to render.
it("renders without crashing", () => {
const div = document.createElement("div");
ReactDOM.render(<MyFirstComponent />, div);
});
// We can test the rendering behavior by using
// document.createElement('div'), to create a div,
// and passing an instance of `<MyFirstComponent />`
// and the div element as arguments to `ReactDOM.render`.
//
// Give this a test a name like "renders without crashing".
// Note that tests are in the format:
//
// it('test name', () => {
// });
it("sets up the initial state", () => {
const component = shallow(<MyFirstComponent />);
const initialState = {
hello: "world",
ping: null
};
expect(component.state()).toEqual(initialState);
});
// Next, we should test that the initial state is
// correctly set for this component. To do so,
// we'll use the `shallow` function, passing in
// `<MyFirstComponent />`.
//
// Checking state can be done with the state function:
// call `.state(key)` on your shallow component, and
// check that it is `.toEqual` to the correct value.
// We'll use `expect` here, so the final format should
// be:
//
// expect(<shallow_component>.state(key)).toEqual(value)
it("can find the string Hello!", () => {
const component = shallow(<MyFirstComponent />);
expect(component.contains("Hello!")).toEqual(true);
});
// We can also test for the presence of static text in
// the component. Create another shallow component and
// test for the presence of the string "Hello!". We'll
// use `.contains` for this:
//
// expect(<shallow_component>.contains(<text>)).toEqual(true);
// Finally, let's test that the button in our component
// actually makes a change to the component itself.
// Create a shallow component, and find the button on it:
//
// <shallow_component>.find('tagname')
//
// With this button, we should first make sure it exists
// by expecting it to have a length of 1 (one element was
// found). We can click the button using simulate-try
// simulating a 'click' event:
//
// element.simulate(event)
//
// Finally, we should ensure that doing this actually
// changes the state as we'd expect. In this case, we
// just need to check that the value for 'ping' in
// state is now equal to 'pong'.
it("can click the button", () => {
const component = shallow(<MyFirstComponent />);
const button = component.find("button");
button.simulate("click");
expect(component.state().ping).toEqual("pong");
});