(Beta release: Still a work in progress.)
Github pages link:
https://yeahshecodes.github.io/TDD-ReactJS/
Steps for this demo TDD project
- npx create-react-app my-app
- cd my-app
- yarn install
Test the app runs before you start tdd:
- yarn start
TDD
Setup
React TDD Setup
Add enzyme to test for html elements in the components
- yarn add enzyme
- yarn add enzyme-adapter-react-16
Documentation links for Jest & Enzyme
Jest documentation:
https://jestjs.io/docs/en/getting-started.html
Also review the documentation for information on how to create enzyme tests:
Part 1 - A simple hello world test
Begin TDD
Start with your first component and test
Create components folder
- cd src && mkdir components
Create the component hello-world folder
- mkdir hello-world && cd hello-world
Add the test file and the first failing test:
- touch hello-world.test.js
Then run your tests:
- yarn test
This will show 1 failed test, and tell you “Your test suite must contain at least one test.”. This can be our first failing test. The next step is to add in the test script:
Start by adding this to the test file:
import React from "react";
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({adapter: new Adapter() });
describe("<HelloWorld />", () => {
let component = shallow(<HelloWorld />);
it("should render a <HelloWorld /> component", () => {
expect(component).toBeDefined();
});
});
That tests that the
ReferenceError: HelloWorld is not defined
Now add in the component and get the test to pass.
Add to the folder you create a file called “hello-world.component.js” : /my-app/src/component/hello-world/hello-world.component.js
Inside hello-world.component.js add your empty component declaration:
import React from "react";
class HelloWorld extends React.Component {
render() {
}
}
export default HelloWorld;
And to your test add the correct import statement at the top of the file:
import HelloWorld from "./hello-world.component";
Your test should now look like this:
import React from "react";
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import HelloWorld from "./hello-world.component";
configure({adapter: new Adapter() });
describe("<HelloWorld />", () => {
let component = shallow(<HelloWorld />);
it("should render a <HelloWorld /> component", () => {
expect(component).toBeDefined();
});
});
Add your failing tests.
Decide and plan what you want to test:
Write down the steps you want to test.
- Test that the component contains an H1 element
- Test that the H1 element has the text “Hello World”
Create your failing tests:
import React from "react";
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import HelloWorld from "./hello-world.component";
configure({adapter: new Adapter() });
describe("<HelloWorld />", () => {
let component = shallow(<HelloWorld />);
it("should render a <HelloWorld /> component", () => {
expect(component).toBeDefined();
});
it("should contain an H1 element ", () => {
const h1 = component.find("H1");
expect(h1.length).toEqual(1);
});
it("should contain an H1 element with the text 'Hello World!' ", () => {
const h1 = component.find("h1");
const expectedText = "Hello World!";
expect(h1.text()).toEqual(expectedText);
});
});
Write your code to pass the failing tests:
Now you know what you need to add in order for the tests to pass, you now have a step by step guide making sure you write the right code.
- Add an H1 element.
- Add the text Hello World! to the H1 element.
Your code will look something like this:
import React from "react";
class HelloWorld extends React.Component {
render() {
return (
<h1>Hello World!</h1>
)
}
}
export default HelloWorld;
Your tests should noe be passing.
If your tests are not passing check for any syntax issues and make sure you have followed the right steps.
Common issues:
Make sure you have exported your component:
export default HelloWorld;
And imported it inside your test file:
import HelloWorld from "./hello-world.component";
Make sure the extends React.Component part has a capital “C” and not a small “c”:
class ... extends React.Component {
...
Part 2 - Test logic
Lets dig in a little further into TDD and create a new component with some logic.
Your brief: Create a component that displays a list of user names and emails.
Break down the requirement into steps:
- Create a component that takes in a list of data, ie an array.
- The component must use that array to loop over and generate a list.
TDD - Decide on you tests
A common syntax for tests is to write the tests in an “it should x” syntax, eg:
it should render a <UserEmails /> component
Using that syntax we can now write out the test’s steps using that syntax:
- it should render a
component - it should be able to accept an array of user details
- is should be able to use the user details to loop and generate the same number of li elements
Setup the test file
In the compoments folder create a new folder called “user-emails”.
Inside the folder create a file called “user-emails.test.js”.
You should now have a file in this filepath: /my-app/src/component/user-details/user-emails.test.js
Add the initial setup to the file.
You already know what your 3 tests shuold be, you planned them ahead of time:
- it should render a
component - it should be able to accept an array of user details
- is should be able to use the user details to loop and generate the same number of li elements
This translates into this:
import React from "react";
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({adapter: new Adapter() });
describe("<UserDetails />", () => {
let component = shallow(<UserDetails />);
it("should render a <UserEmails /> component", () => {
});
it("should be able to accept an array of user details", () => {
});
it("should be able to use the user details to loop and generate the same number of li elements", () => {
});
});
Now add in your expect statements, and start fleshing out your tests:
Tips:
- Write the minimum test logic to test a single functionality. Don’t over complicate what you want to test.
Your tests can be fleshed out similar to this:
import React from "react";
import { configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({adapter: new Adapter() });
describe("<UserDetails />", () => {
const details = [
{
"name": "James",
"email": "james@email.com"
},
{
"name": "Maggie",
"email": "maggie@email.com"
},
{
"name": "Xolani",
"email": "xolani@email.com"
}
];
let component = shallow(<UserDetails details={details} />);
it("should render a <UserEmails /> component", () => {
expect(component).toBeDefined();
});
it("should be able to accept an array of user details", () => {
expect(component.prop('details')).to.equal(details);
});
it("should be able to use the user details to loop and generate the same number of li elements", () => {
const liElements = component.find('li');
expect(liElements.length).to.equal(3);
});
});
Run your tests:
yarn test
If you need to, run the test command to start running and watching the tests, you should see this :
Your test suite must contain at least one test.
Code the component
Tip
- Code only enough to make the tests pass
Insight: This is how TDD also help keep code lightweight. You know what you need to pass the tests, so you make sure you write only enough to make tests pass. Prevents code bloat. Also help with loose coupling.
Create your file
user-details.component.js
This will be alongside your test, in the following path:
/my-app/src/component/user-details/user-details.component.js
Make note of the steps you have to go through to make the tests pass:
- Create a UserDetails component
- Use the props variable details to retrieve the user details list and loop over
- Create a ul and inside it loop (in this case we can use a simple .map method) and generate the li elements displaying the name and email.
Start writing the code:
Step 1
Create a UserDetails component
import React from "react";
class UserDetails extends React.Component {
render() {
}
}
export default UserDetails;
Remember to add the import to your test file so you can start seeing your tests run:
import UserDetails from "./user-details.component";
Note: It is important you add the import: import UserDetails from “./user-details.component”;
Step 2
Use the props variable details to retrieve the user details list and loop over Create a “ul” and inside it loop (in this case we can use a simple .map method) and generate the “li” elements displaying the name and email.
import React from "react";
class UserDetails extends React.Component {
render() {
return (
<ul>
{
this.props.details.map((d, index) => (<li key={index}>{d.name} - {d.email}</li>))
}
</ul>
)
}
}
export default UserDetails;
Review the requirements
Finally review the requirements, make sure you have met them. Then commit the code, push it up to the branch, create your pull request and complete your feature.
TODO
- Inversion of control examples
- Dependency injection examples
- Mocking examples
- Integration vs Unit Tests