JavaScript Testing Review

 · 3 mins read

I’d like to come back to where we can re-discuss about JavaScript testing, since unit test, tdd and bdd are so popular among us developers. I believe most developers once ran into a situation where we spent more time on testing than developing component itself. I should say no testing is painless. Last week, I got an interview asking me what to test during development. It provokes me thinking this question, and more further to how to mitigate this pain and what is important in tesing.

You created a component, what’s next? From my point of view, there are some aspects of testing we should pay the attention to.

  • inputs and outputs
  • appearance
  • behavior
  • context
  • side effects

Take React testing for example, with testing tools jest, enzyme, react-test-renderer. Let’s create a component named Autocomplete. Here I just show the key parf of my code below:

./components/Autocomplete.js

//...
const addr = "http://localhost:3001/";

export default class extends React.Component {
  state = {
    //...
  };

  cbHandler(item) {
    //setState with setTimeout, because it's called in children
  }

  componentDidMount() {
    console.log('componentDidMount')
    //fetch ...
  }

  inputHandler(event) {
    let value = event.target.value;
    //... fetch
  }

  keypressHandler(event) {
    if (event.charCode === 13) {
      //... fetch
    }
  }

  render() {
    return (
      <StyledComp>
        <input
          type="text"
          value={this.state.value}
          onInput={this.inputHandler.bind(this)}
          onKeyPress={this.keypressHandler.bind(this)}
        />
        <Suggestions data={this.state.data} cb={this.cbHandler.bind(this)} />
      </StyledComp>
    );
  }
}

./components/Suggestions.js

//...

export default class extends React.Component {
  clickHandler(item) {
    console.log("clickHandler");
    this.props.cb(item);
  }

  render() {
    const { data } = this.props;
    return (
        //...
        <ul>
          {data
            ? data.map(e => {
                return (
                  <li key={e.id} onClick={this.clickHandler.bind(this, e)}>
                    {e.name}
                  </li>
                );
              })
            : ""}
        </ul>
    );
  }
}

Writing Test suite

./components/__test__/AutoComp.test.js

testing appearance with jest snapshot:

 describe("Autocomplete appearance", () => {

  it("renders correctly", () => {
    const tree = renderer.create(<Autocomplete />).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

it will output a .snap file for us, ./components/__test__/__snapshots/AutoComp.test.js.snap

So, every time you call expect(tree).toMatchSnapshot(); you want to compare the looks of the rendering component with the snapshot in this file.

testing behavior with enzyme

describe("Autocomplete behavior", () => {
  it("behave correctly", () => {
    const w = mount(<Autocomplete />);
    expect(w.find("input")).toHaveLength(1);

    w.find("input").simulate("input", { target: { value: "abc" } });
    expect(w.state("value")).toEqual("abc");
  });
});

We should avoid testing with context, but if we can’t, just construct one!

If you using Redux to manage your shared states, redux-mock-store is certainly a good choice!

dealing with side effects, like networking fetch.

To simplify, we can write a function to fake fetch by using jest.fn, which helps us to spy function call on parameters, results and so on.

<br />global.fetch = jest.fn(url => {
    let resp = {
        ...
    };
  //Promise.resolve({ blob: blob => resp })
  return Promise.resolve({ json: json => resp });
});

Also, if you don’t want to write this, you could turn to jest-fetch-mock, which allows you to easily mock your fetch calls and return the response you need to fake the HTTP requests.

To install, npm install –save-dev jest-fetch-mock

Then add global.fetch = require(‘jest-fetch-mock’)

to rootDir/setupTests.js to setup the fetch API on global context.

Use fetch.mockResponse(body, init) to mock http reponse.

remember to reset after a mocking, fetch.resetMocks();

JSON Server

Lastly, if you wanna test real requesting, I recommend to use JSON Server for testing REST API, with which you can get http response in JSON format.

Get a full fake REST API with zero coding in less than 30 seconds

GET    /posts
GET    /posts/1
POST   /posts
PUT    /posts/1
PATCH  /posts/1
DELETE /posts/1

After installation, start like this:

json-server -w db.json

But here I want to fake massive outputs, so I use a js file to generate outputs, and then start using this command:

json-server -p 3001 -w data.js

After launch, there should be output like this:

Resources
  http://localhost:3001/users
  http://localhost:3001/meta

  Home
  http://localhost:3001