0.2.8 • Published 5 years ago

mockshot v0.2.8

Weekly downloads
4
License
MIT
Repository
github
Last release
5 years ago

Automatic mocks generation from snapshot tests

GitHub license npm version code style: prettier Tested with Jest

TL;DR

Imagine you could:

  1. Never manually write a mock again
  2. Have a guarantee that your mocks are always valid

Mockshot give you these superpowers, and more.

Overview

Snapshot testing may commonly be known for UI testing, but the mechanism itself can be used to test the shape of any object. Mockshot utilizes the artifacts of snapshot tests to generate mocks. The flow can be summarized:

  1. Write a Jest snapshot test for a method.
  2. Use the snapshot's output as blueprints for generating a mock.
  3. Let other methods use that mock in their tests.

In this flow, we test a method against its own mock, then expose this mock to the world. This means we are shifting the responsibility of generating a mock from the consumer to the source.

This pattern is called Test Coupling since two isolated unit tests are now coupled together by the same mock. A change in the source's interface will lead to a change in the mock and from there - to all consumer's tests.

Your unit test is now powerful as an integration test. Wow.

Usage

install

Mockshot requires the jest testing framework. Installation of Jest is not covered in this document.

After setting up Jest, simply yarn or npm Mockshot

$ yarn install mockshot

Quickstart

1. Write a test:

import "mockshot";
import { SomeClass } from "./SomeClass";

describe("SomeClass", () => {
  it("getSomeData should return the correct shape", () => {
    // Arrange
    const instance = new SomeClass();

    // Act
    const result = instance.getSomeData();
    // result === { foo: "bar" }

    // Assert with Mockshot
    expect(result).toMatchMock(SomeClass, "getSomeData", "success");
  });
});

2. Run the test to generate a snapshot:

$ jest './SomeClass.spec.ts'
 PASS  examples/simple/SomeClass.spec.ts
  SomeClass
    ✓ getSomeData should return the correct shape (5ms)

 › 1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        5.213s
Ran all test suites matching /.\/SomeClass.spec*/i.
✨  Done in 6.80s.

3. Run mockshot to generate a mock:

$ yarn run mockshot

The generated mock is now ready for use in ./mocks/SomeClassMocks.ts:

export class SomeClassMocks {
  static getSomeData(mock: "success"): any {
    switch (mock) {
      case "success":
        return {
          foo: "bar"
        };
      default:
        throw Error("Unknown mock: " + mock);
    }
  }
}

4. Consume the mock

import { UsingSomeClass } from "./UsingSomeClass";
import { SomeClassMocks } from "./mocks/SomeClass";

describe("UsingSomeClass", () => {
  it("Should parse getSomeData", () => {
    // Prepare the stub with our mock data
    const someClassMock = {
      getSomeData: () => SomeClassMocks.getSomeData("success")
    };
    const instance = new UsingSomeClass(someClassMock);

    // Now when UsingSomeClass will call getSomeData it will get { foo: "bar" }
  });
});

Key Features

  1. Mock any Javascript object using custom matchers.
  2. Special matchers for API responses for generating server mocks.
  3. Mocks are using Typescript for enhanced auto-complete.
  4. Special generators for class mocks, API server mocks and more.

Mockshot, In Depth

Introduction

Mockshot is an extension over Jest's snapshot mechanism. It uses toMatchSnapshot under the hood to create special annotated snapshots that can later be used buy a generator to construct JS or TS objects using AST.

Matchers

Matchers are the assert methods used during the test to generate the mock blueprints. They serialize the object and prepare its shape. We currently have 2 matchers:

toMatchMock - for mocking class method

toMatchMock(className: string, methodName: string, mockName: string, ignorePath: any[])

className: string | Object

The name of the class to mock. You can supply a string but it is better to provide the actual class. Mockshot will detect the class's name by looking at cls.constructor.name.

methodName: string

The name of the method response to mock. Unfortunately we can't auto detect the name of the method just by looking at its reference, so make sure this string is correct.

mockName

The name of the mock. Since each method can have multiple responses, we provide a name for each mock, ie - success or empty-list or error are some examples.

ignorePath

An array of paths to ignore. Since snapshot matches the objects, a non constant values like id or timestamp will change from test to test. You can provide an ignore path to ignore the content of this values. In that case, Mockshot will only check the existence of the keys and the type of the value, without looking at the content (ie, it will verify there is a key named id with value type string).

const value = SomeClass.getSomething();
expect(value).toMatchMock(SomeClass, "getSomething", "success", ["id"]);

toMatchApiMock - for mocking HTTP response

Same as previous matcher, but designed especially to serialize an HTTP response. In that case you don't need to supply a class or method name, since all required data as path, status and methods is infered from the object.

const value = await axios.get("http://somewhere.com/user");
expect(value).toMatchMock(value);

This will generate the following mock:

export class API {

    static get<T extends keyof getResponses>(url: T): getResponses[T] {
        switch (url) {
            case "/user":
                return { success:
                   { body:
                      { data: /* Response serialized here */ }
                    status: 'OK' },
                    statusCode: 200 } }
}

You can then use the mock:

import { API } from "./mocks";

const response = API.get("/user", success);
/**
 * response is now the same HTTP Response object as we had before
 * */
axios.get.mockImplementation(()=>response)

Generator

After you've created the snapshots you will run the generator. It will traverse the codebase, looking for snapshots created by Mockshot and generate and actual Typescript and Javascript files with your mocks, depending on their type (methods or api).

TBA