npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

testtriple

v3.1.1

Published

A handy little mocking library inspired by testdouble and @fluffy-spoon/substitute

Downloads

189

Readme

testtriple

A handy little mocking library inspired by testdouble and @fluffy-spoon/substitute. It's main features are:

  • easily creating nested mocks
  • inferring types wherever possible
  • make it possible to completely separate arrange/assert

testtriple completely focuses on mocking and leaves assertions to your test runner of choice.

installation

npm install testtriple

quick example

import { mock, returns } from "testtriple";

type Human = {
  name: string;
  birthDate: Date;
  getAge: () => number;
  mother: Human;
  father: Human;
};

const bob = mock<Human>({
  name: "bob",
  father: mock({
    birthDate: new Date(1970, 1, 1),
    mother: mock({
      name: "helen",
      getAge: returns(90),
    }),
  }),
});

console.log(bob.name); // "bob"
console.log(bob.father.birthDate); // Date(1970,1,1)
console.log(bob.father.mother.getAge()); // 90

mocking properties

You can use the mock<T>({...[subset of T]...}) function to create an object that only has some values set, but pretends to be a complete object. This can also be nested as deep as desired, whith all types being inferred.

import { mock } from "testtriple";

const bob = mock<Human>({
  father: mock({
    mother: mock({
      name: "helen",
    }),
  }),
});
console.log(bob.father.mother.name); // "helen"

mocking function calls

To mock function calls of an object created with mock({...}) you have to explicitly set the property to a function mock created using one of the functions below:

returns(valueToReturn)

import { mock, returns } from "testtriple";

const bob = mock<Human>({
  getAge: returns(10),
});
console.log(bob.getAge()); // 10

throws(errorToThrow)

import { mock, throws } from "testtriple";

const eve = mock<Human>({
  getAge: throws("You don't ask the age of a women!"),
});
console.log(eve.getAge()); // throws "You don't ask the age of a women!"

resolves(valueToResolve)

import { mock, resolves } from "testtriple";

const bob = mock<Human>({
  getAge: resolves(10),
});
console.log(await bob.getAge()); // 10

rejects(errorToThrow)

import { mock, rejects } from "testtriple";

const eve = mock<Human>({
  getAge: rejects("You don't ask the age of a women!"),
});
console.log(await eve.getAge()); // // throws "You don't ask the age of a women!"

spy(...fns[])

This is the basic function that's used by all function mockers above. It takes one or more mimick functions. On every call of the original function, the next mimick function get's called and it's return value returned.

import { mock, spy } from "testtriple";

const bob = mock<Human>({
  getAge: spy(
    () => 10,
    () => 20,
    () => 30
  ),
});
console.log(bob.getAge()); // 10
console.log(bob.getAge()); // 20
console.log(bob.getAge()); // 30

It can also be used to chain returns,throws,resolves and rejects

import { mock, spy, returns, resolves, throws } from "testtriple";

const bob = mock<Human>({
  getAge: spy(returns(10), resolves(20), throws("he's dead, jim!")),
});
console.log(bob.getAge()); // 10
console.log(await bob.getAge()); // 20
console.log(bob.getAge()); // throws "he's dead, jim!"

verifying calls

testtriple doesn't do any assertions. But it gives you access to function calls and their parameters using callsOf, callsOfAll, callOrderOf, and callDetailsOf. You can then assert these calls using the test runner of your choice.

callsOf(fn)

Used to verify the call order and arguments of a single function.

import { mock, spy, callsOf } from "testtriple";

const math = mock<Calulator>({
  add: spy(),
  multiply: spy(),
});

math.add(1, 2);
math.multiply(2, 2);
math.add(3, 8);

expect(callsOf(math.add)).toStrictEqual([
  [1, 2],
  [3, 8],
]);

callsOfAll(...fns)

Used to verify the call order and arguments across multiple functions.

import { mock, spy, callsOfAll } from "testtriple";

const math = mock<Calulator>({
  add: spy(),
  multiply: spy(),
});

math.add(1, 2);
math.multiply(2, 2);
math.add(3, 8);

expect(callsOfAll(math.add, math.multiply)).toStrictEqual([
  [math.add, 1, 2],
  [math.multiply, 2, 2],
  [math.add, 3, 8],
]);

callOrderOf(...fns)

Used to verify the call order without arguments across multiple functions.

import { mock, spy, callOrderOf } from "testtriple";

const math = mock<Calulator>({
  add: spy(),
  multiply: spy(),
});

math.add(1, 2);
math.multiply(2, 2);
math.add(3, 8);

expect(callOrderOf(math.add, math.multiply)).toStrictEqual([
  math.add,
  math.multiply,
  math.add,
]);

callDetailsOf(fn)

Used to simply verify the number of times a single function was called.

import { mock, spy, callDetailsOf } from "testtriple";

const math = mock<Calulator>({
  add: spy(),
  multiply: spy(),
});

math.add(1, 2);
math.multiply(2, 2);
math.add(3, 8);

const { called, callCount } = callDetailsOf(math.add);

expect(called).toBe(true);
expect(callCount).toBe(2);

comparison to testdouble and @fluffy-spoon/substitute

While testtriple was inspired by the mentioned libraries, it does not mean that it focuses on the same core features. Instead, I tried to address some of weaknesses in those libraries. Specifically the creation of nested mocks in one go, without specifying the types for everything, and infer them instead. That's why the quick example features this functionality.

Doing the same in @fluffy-spoon/substitute would look like this:

const bob = Substitute.for<Human>();
const father = Substitute.for<Human>();
const mother = Substitue.for<Human>();
mother.name.returns("helen");
mother.getAge().returns(90);
father.birthDate.returns(new Date(1970, 1, 1));
father.mother.returns(mother);
bob.father.returns(father);

While it's technically less lines of code, I find it incredibly hard to read. It gets even harder to read, when the type of sub-objects is not importable. Then it would look like this:

const bob = Substitute.for<Human>();
const father = Substitute.for<Human["father"]>();
const mother = Substitue.for<Human["father"]["mother"]>();

So, constructing such nested mocks is clearly the core feature of testtriple. Everything else is kept to a minimum, because other libraries already do it very well.

Assertions are a good example of this. Both testtouble and substitute provide some assertion functions, while testtriple does not. testtriple only gives you some functions to access the order and parameters of function calls and it's up to you how you assert that this data is correct. But for most cases, testtriple coupled with an assertion library should be able to do the job very well.

So when to use what?

  • Do you want to conveniently create complex, nested mocks with as readable code as possible? -> testtriple
  • Do you want to have some more advanced functionality to verify function calls on mocks? -> testdouble/substitute

In every case you'll need some additional assertion library or assertion tools of your testrunner. With testtriple more that with the others.

why is it called testtriple

I was lazy and just took the word double from testdouble, and made it triple instead. But I'm sure you've already got that :D

license

MIT