Matt Arbesfeld CEO @ LogRocket

JavaScript Testing: Chai (Part 1)

2 min read 720

At LogRocket, nearly our entire code base is JavaScript. Over the years, we have learned a good amount about the lesser-known features of JavaScript testing libraries. In this series, I’ll go through the LogRocket testing stack and describe some of the tips and tricks that help make our tests faster and more robust.

  • Part 1: Test Expectations — Chai
  • Part 2: Test Mocks — Sinon
  • Part 2: Test Frameworks — Jest and Mocha
  • Part 3: Integration Testing — WebdriverIO and Selenium

Chai

Chai is a “test expectation” library: it helps you make assertions about code behavior. Chai itself comes in many syntactic flavors that you can choose between for making assertions.

For example, if you wanted to assert that foo is a string, there are a few different options:

// should
chai.should();
foo.should.be.a(‘string’);

// expect
chai.expect(foo).to.be.a(‘string’);

// assert
chai.assert.typeOf(foo, ‘string’);

expect().to.throw()

As JavaScript engineers, we’re really good at making sure our code works when the inputs are correct. The biggest challenge — and one of the best reasons to write tests — is to make assertions for failing or unexpected inputs.

Chai comes with a handy helper that lets us assert that code should throw an exception. This is great for throwing inputs like -1, ★★François★★ and function(){alert()} at code that expects a user’s name.

Here’s an example usage:

expect(() => {
  callSomeFunction();
}).to.throw(/an error/);

equal() vs eql()

Chai comes with a number of built-in functions for determining equality.equal() asserts that two arguments are referentially equal (ie. a === b). eql() does a deep equality check between two arguments.

Example:

expect('rocket').to.equal('rocket') // true
expect('rocket').to.eql('rocket') // true
expect({ log: 'rocket' }).to.equal({ log: 'rocket' }) // false
expect({ log: 'rocket' }).to.eql({ log: 'rocket' }) // true

Why use equal/eql at all instead of expect(foo === 'rocket').to.be.true? We get beautiful test output that looks like this:

Instead of this:

dirty-chai

Chai can be extended with various plugins that provide additional functionality, such as chai-stringwhich adds handy string-testing functionality, chai-as-promisedwhich allows us to write assertions about Promises, and chai-datetimewhich provides date assertions.

One handy, lesser-known plugin is dirty-chai. Normally Chai assertions can only be made like so:

expect(foo).to.be.true

This can be brittle. If I accidentally make a typo, then the assertion will never be checked:

expect(foo).to.be.frue

Instead, we use dirty-chai which extends chai with function calls like so:

expect(foo).to.be.true()

This way, if I ever make a syntax mistake, the test will throw an exception instead of passing silently.

sinon-chai

Sinon is an amazing library for writing test “spies” and “stubs” in JavaScript. With Sinon, we can mock functionality that doesn’t contribute to the test assertion. There are a number of reasons that you might want to use a stub or spy in a test:

  • Fake a network interface to see how a component responds to certain network conditions
  • Mock code that relies on interfacing with the DOM
  • Assert that a certain callback is invoked by a test.

sinon-chai allows us to make Sinon assertions with the Chai syntax:

const stub = sinon.spy();
expect(stub).to.be.calledOnce();

I’ll describe Sinon more thoroughly in another blog post this series.

eslint-plugin-chai-expect

If you’re using eslint to lint your code, eslint-plugin-chai-expect prevents a few of the most common syntax errors:

expect(foo === bar).to.be.true(); // no-inner-compare
expect(foo) // missing-assertion

Have any other lessons learned about assertions with Chai? Let me know in the comments!


Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Matt Arbesfeld CEO @ LogRocket

Leave a Reply