DEV Community

John Au-Yeung
John Au-Yeung

Posted on • Originally published at thewebdev.info on

JavaScript Unit Test Best Practices — Hooks and APIs

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

Even more articles at http://thewebdev.info/

Unit tests are very useful for checking how our app is working.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices we should follow when writing JavaScript unit tests.

Properly Set Up the Actions that Apply to All the Tests Involved

If we’re running the same thing before every test, we should put it in a beforeEach hook.

This way, we run the same piece of code before each test without repeating the code.

For example, we can write:

describe('Saving the user profile', () => {

  beforeEach(() => {
    login();
  });

  it('should save updated profile setting to database', () => {
    //...

    expect(request.url).toBe('/profiles/1');
    expect(request.method).toBe('POST');
    expect(request.data()).toEqual({ username: 'james' });
  });

  it('should notify the user', () => {
    //...
  });
});

  it('should redirect user', () => {
    //...
  });
});

Likewise, if we have some code that we’ve to run after each test, we should have an afterEach hook that runs after each test:

describe('Saving the user profile', () => {

  beforeEach(() => {
    login();
  });

  afterEach( () => {
    logOut();
  });

  it('should save updated profile setting to database', () => {
    //...

    expect(request.url).toBe('/profiles/1');
    expect(request.method).toBe('POST');
    expect(request.data()).toEqual({ username: 'james' });
  });

  it('should notify the user', () => {
    //...
  });

  it('should redirect user', () => {
    //...
  });
});

Consider Using Factory Functions in the Tests

Factory functions can help reduce setup code.

They make each test more readable since creation is done in a single function call.

And they provide flexibility when creating new instances.

For instance, we can write:

describe('User profile module', () => {
  let user;

  beforeEach(() => {
    user = createUser('james');
  });

  it('should publish a topic "like" when like is called', () => {
    spyOn(user, 'notify');
    user.like();
    expect(user.notify).toHaveBeenCalledWith('like', { count: 1 });
  });

  it('should retrieve the correct number of likes', () => {
    user.like();
    user.like();
    expect(user.getLikes()).toBe(2);
  });
});

We have the createUser function to create a user with one function call.

This way, we don’t have to write the same setup code for every test.

We can also use them with DOM tests:

function createSearchForm() {
  fixtures.inject(`<div id="container">
    <form class="js-form" action="/search">
      <input type="search">
      <input type="submit" value="Search">
    </form>
  </div>`);

  const container = document.getElementById('container');
  const form = container.getElementsByClassName('js-form')[0];
  const searchInput = form.querySelector('input[type=search]');
  const submitInput = form.querySelector('input[type=submit]');

  return {
    container,
    form,
    searchInput,
    submitInput
  };
}

describe('search component', () => {
  describe('when the search button is clicked', () => {
    it('should do the search', () => {
      const { container, form, searchInput, submitInput } = createSearchForm();
      //...
      expect(search.validate).toHaveBeenCalledWith('foo');
    });

    // ...
  });
});

We have the search form creation code in the createSearchForm function.

In the function, we return various parts of the form’s DOM objects to let us check the code.

Using Testing Framework’s API

We should take advantage of a test framework’s API.

This way, we make use of its functionality to make testing easier.

For example, we can write:

fit('should call baz with the proper arguments', () => {
  const foo = jasmine.createSpyObj('foo', ['bar', 'baz']);
  foo.baz('baz');
  expect(foo.baz).toHaveBeenCalledWith('baz');
});

it('should do something else', () => {
  //...
});

using Jasmine.

We spied on stubbed functions to see if they’re called with the createSpyObj method.

And we use fit so that only the first test is run.

Conclusion

We should make sure of the testing framework’s API to make testing easier.

Also, we should make sure we put repeated code in hooks to avoid repetition.

The post JavaScript Unit Test Best Practices — Hooks and APIs appeared first on The Web Dev.

Top comments (0)