![Earl](https://raw.githubusercontent.com/krzkaczor/earl/master/docs/images/gh-cover.png)
Ergonomic, modern and type-safe assertion library for TypeScript
Brings good parts of Jest back to good ol' Mocha
Features
- 💪 Powerful validators and matchers
- 🤖 Type-safe - written in TypeScript and goes well with static analysis
- ✍ AutoFix - magically writes missing assertions for you
- 🎭 Builtin support for mocks
- 🏃♂️ Integration with test runners (mocha)
Installation
npm install --save-dev earljs
Example
import { expect } from 'earljs'
expect(response).toEqual({ body: { trimmed: true, timestamp: expect.a(String) } })
Motivation
I used to love mocha + chai combo, but as time flew, I felt it's limiting. Other projects like Jest shown that there is
room for innovation in this space. With last version published 2 years ago, Chai
seems abandoned. Furthermore, as
TypeScript becomes more and more popular, it became evident that some things about writing assertions could be improved.
earl is an effort to bring a little bit of innovation in the space of assertion libraries.
Why not just Jest?
I really enjoy some of the Jest's features — that's what inspired this library in the first place. However, I really
hate others. Jest feels too magical and
full of bugs for my
taste. On the other hand, I always enjoyed simplicity and confidence that Mocha provides.
Simply put, Jest takes control away from you, Mocha puts you in charge.
Features
💪 Powerful validators and matchers
Validators like toEqual
or toThrow
are acting as advanced assertions. They can be combined with matchers like
expect.anything()
to match whole ranges of values. Allowing, for example to easily assert not fully deterministic
objects. Unlike chai-subset
using this asserts much more info about actual object shape.
expect({
data: { id: 5, name: 'Kris' },
timestamp: '05/02/2020 @ 8:09am (UTC)',
}).toEqual({
data: { id: expect.a(Number), name: 'Kris' },
timestamp: expect.a(String),
})
🤖 Type-safe (support for TypeScript) and goes well with static analysis
Validators are typesafe by default ex. when comparing objects with toEqual they have to have same types. Furthermore,
validators are always functions, not properties like in chai
. This goes well with no-unused-expressions
eslint rule.
expect(5).toEqual('abc')
✍️ AutoFix
Automatically fix expected (if omitted) values to match actual. Works with different validators.
Implementation requires stack traces with correct sourcemaps - available in 99% environments. This feature is inspired
by Jest's inline snapshots.
expect(serverResponse).toEqual()
expect(serverResponse).toEqual({ users: [{ name: 'Kris Kaczor' }] })
✨ Driven by you
Yes you! Help us to guide it's future development! If you like what you see give us a 🌟. Don't hesitate to create issue
in this project or reach out me directly on twitter (@krzkaczor). Take a look at our
roadmap.
API
Validators are advanced assertions, most of them work with additional matchers.
Validators
toEqual(object)
- performs deep equality check, ensures type equality, supports additional matcherstoLooseEqual(object)
- like toEqual but without type checkingtoThrow(expectedErrorMsg?: string)
- checks if expected error was throws. Requires checked value to be a
parameterless function.toBeRejected(expectedErrorMsg?: string)
- checks if promise was rejected with a expected error. Note: this validator
returns another promise that needs to be handled properly (awaited or returned from test case). To avoid mistakes use
no-floating-promises
eslint rule (it's part of TypeSTRICT).toBeExhausted()
- checks if given mock is exhausted. Works both with strict and loose mocks.
Matchers
Matchers are used to match range of values. These should be combined with validators like toEqual
or strictMocks's
expectedCall
.
anything()
- matches anythinga(class)
- matches any instance of a class. Works as expected with primitives like String, Number etc. Use
a(Object)
to match any object (won't match null). Note: it doesn't work with TypeScript types because they are
erased from the output - you need a JS class.stringContaining(substring)
- matches any string containing given substringnumberCloseTo(expected, delta)
- matches any number within proximity of expected number
Modifiers
not
- will make expectation fail when it should succeed and succeed when it should fail
Mocks
Currently earl features two types of mocks:
strictMocks
are well defined mocks with expected calls and responses defined up frontlooseMocks
are more traditional mocks similar to sinon/jest.
Both types of mocks are automatically verified (isExhausted
check) if test runner integration is enabled.
Examples:
import { expect, strictMockFn } from 'earljs'
const mock = strictMockFn<[number], string>()
mock.expectedCall(1).returns('a')
mock.expectedCall(2).returns('b')
mock.expectedCall(expect.a(Number)).returns('c')
expect(mock(1)).toEqual('a')
expect(mock(2)).toEqual('b')
expect(mock(5)).toEqual('c')
expect(mock(1)).toThrow()
expect(mock).toBeExhausted()
Test runner integration
By integrating with a test runner you get:
- automatic mocks verification after each test
Currently only integration with mocha
is supported. To enable, simply require earljs/mocha
with mocha, you can put
it in .mocharc.js
:
module.exports = {
require: ['earljs/mocha'],
}
🛣️ Roadmap
To help us prioritize future work you can vote with GH reactions 👍
✨ Contributors
Our contributing guide.
Thanks goes to these wonderful people (emoji key):
Contributions of any kind welcome!
Earl logo by @sz-piotr
License
Kris Kaczor MIT