Dojo : TDD Against the Time with React and Redux
This project is maintained by Bogala
We need to begin with a mocked interface
Please replace the current loader in the application by :
if you do not master React, here are some things that might help you
Before all, we need gherkin testing model (cucumber). If you use VSCode, install Cucumber (Gherkin) Full Support
code --install-extension alexkrechik.cucumberautocomplete
For webstorm, you can use Gherkin extension.
Next step is to install jest-cucumber :
yarn add jest-cucumber -D
Usually, we do not use Gherkin and BDD for interface tests. We only test behaviors (BDD = Behavior Driven Development).
Here, for the needs of the dojo, we will bypass this rule a little bit and we will test interface in feature
files.
Lets create a new folder under src
: grid
and a first file grid.feature
Feature: Langton ant workspace
Scenario: My initial conditions
When I launch application
Then I have a grid with 21 lines, 21 cells each line and an ant at the middle
And I have a Material AppBar with title "Langton Ant"
And I have a play button on AppBar
To test this gherkin scenario, we need a feature implementation file grid.feature.spec.tsx
import 'jest-enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { configure, shallow } from 'enzyme';
import { defineFeature, loadFeature } from "jest-cucumber";
const feature = loadFeature("./src/grid/grid.feature");
defineFeature(feature, test => {
test("My initial conditions", ({ given, when, then, pending }) => {
when("I launch application", () => {
pending();
});
then(/^I have a grid with (.*) lines, (.*) cells each line and an ant at the middle$/, (lines, cells) => {
pending();
});
then(/^I have a Material AppBar with title "(.*)"$/, title => {
pending();
});
then("I have a play button on AppBar", () => {
pending();
});
});
});
A simple function is enough to create a component
export default () => (
<div>
<span>It works !</span>
</div>
)
The returned node (div here) isn’t a real html, it’s a TSX object. TSX allows us to describe our User Interfaces (UIs) in a syntax very close to the HTML that we are used to. It is a Typescript representation of the Document Object Model (DOM).
If you want to define a new component, please name it in Pascal Case (ex: MyNewComponent). JSDOM recognizes that it is a React component if it starts with a capital letter.
const ChildComponent = () => (
<span>Child</span>
)
const ParentComponent = () => (
<div>
<ChildComponent />
</div>
)
Props are the attributes for your component. To use it, add a props arg object :
const Image = (props: any) => (
<img src={props.img} />
)
And you can define it in the parent component
const Parent = () => (
<div>
<Image img="./img/picture.png" />
</div>
)
If you have TSLint, any type for props is not a good idea. To be safe, we can define an interface :
interface ImageProps {
img: string;
}
With this, the img attribute became mandatory. To make it optional, you have to add a ?
like this :
interface ImageProps {
img?: string;
}
if the you have an optional prop, it can be a good idea to make a default value.
const Image = ({img = './img/picture.png'}: ImageProps) => (
<img src={img} />
)
const Parent = () => (
<div>
<Image />
</div>
)
You can add 2 types of props :
- objects to transmit data from parent to child
- functions to transmit event from child to parent
This is the React’s one-way binding pattern.
Before all, you have to configure your test file to run with enzyme. Please use de follwing lines :
import 'core-js';
import 'jest-enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import * as React from 'react';
import { configure, mount } from 'enzyme';
// tslint:disable-next-line:no-any
configure({ adapter: new Adapter() });
This code will configure your enzyme environment with the React 16 configuration.
To test a component with enzyme, we have 2 options :
const wrapper = shallow(<MyComponent />, { context: {} });
const wrapper = mount(<MyComponent />, { context: {} });
To find an element, use find(Selector)
You can elements by :
// compound selector expect(wrapper.find(‘div.some-class’)).toHaveLength(3);
// CSS id selector expect(wrapper.find(‘#foo’)).toHaveLength(1);
* Component Constructors:
```jsx
import Foo from '../components/Foo';
const wrapper = shallow(<MyComponent />);
expect(wrapper.find(Foo)).toHaveLength(1);
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('Foo')).toHaveLength(1);
const wrapper = shallow(<MyComponent />);
expect(wrapper.find({ prop: 'value' })).toHaveLength(1);
To get props, you can use the props or prop function :
const wrapper = shallow(<Image img="./test.png" />);
expect(wrapper.props().img).toBe('./test.png');
expect(wrapper.prop('img')).toBe('./test.png');
To get state, it’s like prop function
const wrapper = shallow(<MyComponent />);
expect(wrapper.state('count')).toBe(0);
To simulate an event, use simulate function
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
const { count } = this.state;
return (
<div>
<div className={`clicks-${count}`}>
{count} clicks
</div>
<a href="url" onClick={() => { this.setState({ count: count + 1 }); }}>
Increment
</a>
</div>
);
}
}
const wrapper = shallow(<Foo />);
expect(wrapper.find('.clicks-0').length).toEqual(1);
wrapper.find('a').simulate('click');
expect(wrapper.find('.clicks-1').length).toEqual(1);
expect(wrapper.state('count')).toEqual(1);
Here we simulate a click (no parameter), if we want to simulate an onChange, we use it:
wrapper.find('input').simulate('onChange', 'my new value');
Now your grid is ready, you can go to the next step : First rules and Component State