DEV Community

loading...

Using hanbi for your JavaScript testing

James Garbutt
Often found tinkering with the modern web, web components and build tooling.
・3 min read

This is just a brief write-up of why you might want to use hanbi and how.

What is it?

When writing tests in JavaScript, it is inevitable that you'll eventually want to mock/stub out some functionality in your code.

hanbi is one of the many solutions to this - a small library for stubbing out and spying on functions.

Why?

Most people these days immediately install their go-to testing stack without a second thought when creating a new repository. Often, this of course means installing "sinon".

It is a good library, but it has grown over the years to become very bloated... trying to solve everyone's problems in one go and trying to cover every possible use case.

Just a few of the reasons I wanted to move off it:

  • It is hundreds of KiB in size (not such a problem as a dev-dependency, but does not scale well in CI)
  • It tries to be too helpful (calledThrice is too far)
  • It doesn't play nice with TypeScript (the types work, but they are far too over-complicated and have to be)

For these reasons and more, I just wanted a light alternative which was focused on only handling stubs/spies. No extra help, no 'magic', no shortcuts. Just a focused base implementation.

Some advantages

I love focused libraries which do less but do it well. Examples are esbuild, rollup, prettier and so on.

Hanbi is just the same, very focused with an aim to stay that way.

It is also written in TypeScript, and in a way which means its types work very smoothly and logically. Less crazy types = better.

How to use it

Using hanbi is incredibly simple.

It provides us a way to create a "spy" which monitors its own calls, and a way to create a "stub" which effectively replaces some function with a spy.

We can install it pretty easily:

$ npm i -D hanbi
Enter fullscreen mode Exit fullscreen mode

Spying

For when we want to track the calls of an anonymous function (like an event handler), we can simply create a "spy".

These spies can then be passed in to whatever functionality you're trying to test as if they were their "real" counterparts.

const spy = hanbi.spy();
window.addEventListener('load', spy.handler);
spy.called; // true once the event fires
Enter fullscreen mode Exit fullscreen mode

This is useful when we need to pass in a callback function, test an event handler or something similar.

Stubbing

For when we need to mock out a piece of functionality which we are expecting to trigger, we can stub it.

For example, preventing an object from sending an actual request off or similar.

class Foo {
  myMethod() {
    return 5;
  }

  myOtherMethod() {
    return this.myMethod();
  }
}
const instance = new Foo();
const stub = hanbi.stubMethod(instance, 'myMethod');

instance.myMethod(); // undefined
instance.myOtherMethod(); // undefined

stub.returns(6);

instance.myMethod(); // 6
instance.myOtherMethod(); // 6

stub.called; // true
Enter fullscreen mode Exit fullscreen mode

Assertions/state

There are a few main assertions and pieces of state you can use on a spy/stub:

const spy = hanbi.spy();

// Gets a specific call
const call = spy.getCall(2);

call.args; // The arguments passed when it was called
call.returnValue; // The value that was returned
call.thisValue; // The `this` at the time it was called

// Determines if the spy was called
spy.called;

// Determines if the spy was called with specific args
spy.calledWith('arg1', 'arg2');

// Determines if the spy ever returned a specific value
spy.returned('foo');
Enter fullscreen mode Exit fullscreen mode

Wrap-up

Again, this isn't meant to be taking on all those existing testing libraries you make use of today. I still contribute to sinon and keep its TypeScript types well maintained when I can.

This is just a far more focused alternative for anyone interested.

I came up with it primarily to solve a problem I had: sinon was slowing down CI builds to a noticeable amount, and was only used for a fraction of its functionality.

If you give it a try, do let me know your thoughts and feel free to make any suggestions in the GitHub issues.

Discussion (0)