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

mlok

v0.1.5

Published

TypeScript mocking library focused on simplicity and testing DX

Downloads

431

Readme

🦎 Mlok

TypeScript mocking library focused on simplicity and testing DX

Getting started

npm i -D mlok
import mlok from 'mlok'

const catRepositoryMock = mlok<CatRepository>()

This package is native ESM and does not provide a CommonJS export. If your project uses CommonJS, you will have to convert to ESM or use the dynamic import() function.

Motivation

Some things are difficult to mock. This difficulty often arises due to poor design on our part, but there are also cases where we have limited control over it, such as with frameworks or other third-party library APIs. So, how is such code tested?

  1. It's not Often a pragmatic solution, ideally applied only to the residue of code that is no longer logically reducable or splittable.
  2. Uses real thing in an integration test This approach works well in certain situations, such as when running tests against a dockerized database or a sandboxed service environment. However, it's not always available or efficient and test performance becomes an issue when overused.
  3. Write a complex mock and a unit test Requires lot of effort and resulting mocks are usually highly coupled with the implementation.

While unit tests are not the only type of testing to do, it should be the easiest. Mlok enables you to write mocks for complex inputs (like Request object, database connection, SDK instance) with the same ease as passing in a number or string. Stop worrying about creating mocks that "don't crash", focus on the behavior specific to your tests. Stop saying something is not tested, because...

Example

In the simple snippet an AuthenticationService gets a UserRepository injected. Then it is able to provide a User from the repository based on the bearer token provided from the HTTP request from the Nest.js-like context.

export class AuthenticationService {
  constructor(private readonly userRepository: UserRepository) {}

  public async authenticate(ctx: ExecutionContext): Promise<User | null> {
    const token = this.extractJWT(ctx)
    if (!token) {
      return null
    }
    return this.userRepository.getUserByToken(token)
  }

  private extractJWT(ctx: ExecutionContext): string | null {
    const [, token] =
      ctx
        .switchToHttp()
        .getRequest()
        .headers.authorization?.toString()
        ?.match(/bearer (.*)/i) ?? []
    return token ?? null
  }
}

Let's write a small test: When request sends bearer token foo, user is retrieved by the token. It does not test all the logic, but it focuses on the token extraction and communication with the repository. Let's implement the tests in Jest.

Jest

it('Jest', async () => {
  const ctx: ExecutionContext = {
    switchToHttp: () => ({
      getRequest: () =>
        ({ headers: { authorization: 'Bearer foo' } } as IncomingMessage),
    }),
  }
  const userRepository: UserRepository = {
    getUserById: jest.fn<any>(),
    getUserByToken: jest.fn<any>(),
  }
  await new AuthenticationService(userRepository).authenticate(ctx)
  expect(userRepository.getUserByToken).toHaveBeenCalledWith('foo')
})
  • 💔 Lot of explicit code needs to be implemented "not to crash" (ctx needs to return object with callable switchToHttp, needs to return object with callable getRequest, which needs to have property headers).

  • 🙈 For rich interfaces, we need to either cast type (as IncomingMessage) or implement the whole behavior (UserRepository).

Jest with Mlok

it('Mlok', async () => {
  const userRepository = mlok<UserRepository>()
  const ctx = mlok<ExecutionContext>().override({ authorization: 'Bearer foo' })
  await new AuthenticationService(userRepository).authenticate(ctx)
  expect(userRepository.getUserByToken).toHaveBeenCalledWith('foo')
})
  • 💚 Mock does not fail on default, all you need is mlok<T>(), no need to implement whole interface nor type cast
  • ✅ Focus on what you need to test

Goals

  • 💪 Allow developers to unit tests any contract-designed API with zero mocking effort
  • ✅ Require developers to define only what they need to test, never what they need to create a valid mock
  • 🔁 Integrate easily with existing toolchain (testing & assertion frameworks)

TODO

  • [x] npm + GitHub metadata, keywords etc
  • [ ] docs pages?
  • [ ] Examples and DX comparison
  • [x] function override
  • [x] scalar override
  • [ ] Strict override API
  • [ ] collision API
  • [x] README
  • [ ] Sinon API / comparison
  • [x] Jest + Vitest battery test
  • [ ] Fix test suites for Vitest<=0.20 & Jest <= 24 (probably not issue of functionality)