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

@selfage/test_matcher

v2.1.6

Published

A pattern to match actual value with expected in tests.

Downloads

967

Readme

@selfage/test_matcher

Install

npm install @selfage/test_matcher

Overview

Written in TypeScript and compiled to ES6 with inline source map & source. See @selfage/tsconfig for full compiler options. Provides basic assertions and matchers, which can be easily extended to custom data structures, promoting a framework or a pattern for matching actual value with expected in tests.

Basic matchers

A matcher means a function that returns MatchFn<T> such as eq() and containStr(). It's best to be used together with assertThat() to populate error messages, and reads more naturally.

// import other stuff...
import {
  assertThat, eq, containStr, isArray, isSet, isMap
} from "@selfage/test_matcher";

// Usually called within a test case, if assertThat() fails, it throws an error
// which fails the rest of the test case.

// Equality is checked via `===`. Any type can be compared. The last argument
// `targetName` is filled into the error message `When matching ${targetName}:`,
// when assertion failed. In a test case, you can simply use the variable name,
// or anything you want to help you understand which assertion failed. You will
// see more why this is helpful in debugging when customizing matchers.
assertThat(actual, eq(expected), `actual`);

// `actual` must be a string that contains the expected text.
assertThat(actual, containStr('expected text'), `actual`);

// `actual` can be anything and will be ignored or always succeed.
assertThat(actual, ignore(), `actual`);

// `actual` must be an array of only one type.
// isArray() takes an array of matchers to match each element from `actual`.
assertThat(actual, isArray([eq(1), eq(2)]), `actual`);
// Therefore it can also be used like this.
assertThat(
  actual,
  isArray([containStr('expected1'), eq('expected2'), ignore()]),
  `actual`);
// If `actual` is undefined, it can be matched as the following.
assertThat(actual, isArray(), `actual`);

// `actual` must be a Set of only one type.
// isSet() also takes an array of matchers just like isArray() to match Set in
// insertion order.
assertThat(actual, isSet([eq(1), eq(2)]), `actual`);
// If `actual` is undefined, it can be matched as the following.
assertThat(actual, isSet(), `actual`);

// `actual` must be a Map of one key type and one value type.
// isMap() takes an array of pairs of matchers, to match key and value in
// insertion order.
assertThat(
  actual,
  isMap([[eq('key'), eq('value')], [eq('key2'), eq('value2')]]),
  `actual`);
// If `actual` is undefined, it can be matched as the following.
assertThat(actual, isMap(), `actual`);

Assert upon error

Often we need to assert when a function throws an error, which can be helped by assertReject(), assertThrow(), and eqError().

import {
  assertThat, assertReject, assertThrow, eqError
} from '@selfage/test_matcher';

// Suppose we are in an async function.
let e = await assertReject(() => {
  return Promise.reject(new Error('It has to fail.'));
});
// `eqError()` expects the `actual` to be of `Error` type, to have the expected
// error name, and to contain (not equal to) the error message.
assertThat(e, eqError(new Error('has to')), `e`);

// If the function doesn't return a Promise.
let e = assertThrow(() => {
  // Suppose we are calling some function that would throw an error.
  throw an Error('It has to fail.');
});
assertThat(e, eqError(new Error('has to')), `e`);

Customized matcher

Once you have your own data class, it becomes a pain to match each field in each test. A customized matcher can help to ease the matching process.

import {
  assert, assertThat, eq, isArray, MatchFn
} from '@selfage/test_matcher';

// Suppose we define the following interfaces as data classes.
interface User {
  id?: number;
  name?: string;
  channelIds?: number[];
  creditCard?: CreditCard[];
}

interface CreditCard {
  cardNumber?: string;
  cvv?: string;
}

// The eventual goal is to define a matcher that works like the following, where
// both `actual` and `expected` follow the interface definition, but are of
// different instances, i.e., they are not equal by `===`.
assertThat(actual, eqUser(expected), `actual`);

// The `T` in `MatchFn<T>` is the usually the type of the actual value you want
// to match. In this case, `T` should be `User`. eqUser() is just an example
// name which is really up to you. But following this naming convention makes
// the assertion statement reads more naturally.
function eqUser(expected?: User): MatchFn<User> {
  // `MatchFn<T>` is just an alias of a function type.
  // type MatchFn<T> = (actual: T) => void;
  // You will need to compare the actual value with the expected value in the
  // function body, and throw an error if anything is not matched, instead of
  // returning a boolean if you were wondering.
  return (actual) => {
    // When using assertThat(), the last argument `targetName` is used to
    // construct `When matching ${targetName}:` and it needs to be descriptive
    // enough for you to locate the failure within this matcher, because
    // `eqUser()` can be used inside other matchers, such as isArray().
    if (expected === undefined) {
      // Supports matching when we expect `actual` to be undefined.
      assertThat(actual, eq(undefined), `nullity`);
    }
    assertThat(actual.id, eq(expected.id), `id field`);
    assertThat(actual.name, eq(expected.name), `name field`);
    // Because we can expect `actual.channelIds` to be undefined, the expected
    // array needs to be undefined in that case as well.
    let channelIds: MatchFn<number>[];
    if (expected.channelIds) {
      // Because isArray() takes an array of matchers, we need to convert
      // `expected.channelIds` into `MatchFn<number>[]`.
      channelIds = expected.channelIds.map((channelId) => eq(channelId));
    }
    assertThat(actual.channelIds, isArray(channelIds), `channelIds field`);
    // Similarly, let's convert `expected.creditCards` into
    // `MatchFn<CreditCard>[]`.
    let creditCards: MatchFn<CreditCard>[];
    if (expected.creditCards) {
      // Well eqCreditCard() doesn't exist. Let's define it below.
      creditCards = expected.creditCards.map(
        (channelId) => eqCreditCard(channelId)
      );
    }
    assertThat(actual.creditCards, isArray(creditCards), `creditCards field`);
  };
}

function eqCreditCard(expected?: CreditCard): MatchFn<CreditCard> {
  return (actual) => {
    if (expected === undefined) {
      assertThat(actual, eq(undefined), `nullity`);
    }
    assertThat(actual.cardNumber, eq(expected.cardNumber), `cardNumber field`);
    assertThat(actual.cvv, eq(expected.cvv), `cvv field`);
    // If there are no exisitng matchers to help, you can fallback to use
    // `assert()`.
    assert(
      /^[0-9]$/.test(actual.cardNumber),
      `cardNumber to be of numbers only`,
      actual.cardNumber
    );
    // Or fallback to simply throw an error, if we re-write the above assertion
    // as the following.
    if (!(/^[0-9]$/.test(actual.cardNumber))) {
      throw Error(
        `Expect cardNumber to be of numbers only but it actually is ` +
        `${actual.cardNumber}.`
      );
    }
  };
}

// The input to a matcher is also up to you, as long as it returns `MatchFn<T>`.
// Hard-coded expected user.
function eqACertainUser(): MatchFn<User> {
  return (actual) => {
    // Match `actual` with a const User instance.
  };
}
assertThat(actual, eqACertainUser(), `actual`);
// Options to ignore certain fields.
function eqUserWithOptions(expected?: User, ignoreId: boolean): MatchFn<User> {
  return (actual) => {
    // Same as `eqUser()` except don't assert on id field, if `ignoreId` is
    // true.
  };
}
assertThat(actual, eqUserWithOptions(expected, true), `actual`);

Async matcher

There are no out-of-the-box matchers that are async. But in case you need customized matchers to do async things, such as reading from files, you can implement AsyncMatcherFn<T> which returns Promise<void>, and assert with asyncAssertThat().

function asyncEq(expected: number): AsyncMatcherFn<number> {
  return async (actual) => {
    await ...
  }
}

await asyncAssertThat(actual, asyncEq(expected), `actual`);