1.1.0 • Published 8 months ago

@jaypie/testkit v1.1.0

Weekly downloads
-
License
MIT
Repository
-
Last release
8 months ago

Jaypie Testkit šŸ¦ā€ā¬›šŸ«’

Test utilities built for Jaypie

šŸ“‹ Usage

Installation

npm install --save-dev @jaypie/testkit

Example

Mocking Jaypie

The testkit provides a complete mock for Jaypie including:

  • Log spying (expect(log.warn).toHaveBeenCalled())
  • Default responses for runtime-only functions (connect, sendMessage, submitMetric)
  • No automatic error handling for handlers (which is good in production but obfuscates tests)
  • Most non-utility functions are mocked to allow simple testing
vi.mock("jaypie", async () => vi.importActual("@jaypie/testkit/mock"));

Error Spying

import { ConfigurationError } from "@jaypie/core";

vi.mock("jaypie", async () => vi.importActual("@jaypie/testkit/mock"));

test("ConfigurationError", () => {
  try {
    throw new ConfigurationError("Sorpresa!");
  } catch (error) {
    expect(error).toBeJaypieError();
    expect(ConfigurationError).toHaveBeenCalled();
  }
});

Log Spying

import { log } from "jaypie";

vi.mock("jaypie", async () => vi.importActual("@jaypie/testkit/mock"));

afterEach(() => {
  vi.clearAllMocks();
});

test("log", () => {
  expect(vi.isMockFunction(log.warn)).toBe(true);
  expect(log.warn).not.toHaveBeenCalled();
  log.warn("Danger");
  expect(log.warn).toHaveBeenCalled();
  expect(log.error).not.toHaveBeenCalled();
});

šŸ‘ŗ Logging Conventions:

  • Only use log.trace or log.var during "happy path"
  • Use log.debug for edge cases
  • Now you can add an "observability" test that will fail as soon as new code triggers an unexpected edge condition
describe("Observability", () => {
  it("Does not log above trace", async () => {
    // Arrange
    // TODO: "happy path" setup
    // Act
    await myNewFunction(); // TODO: add any "happy path" parameters
    // Assert
    expect(log).not.toBeCalledAboveTrace();
    // or individually:
    expect(log.debug).not.toHaveBeenCalled();
    expect(log.info).not.toHaveBeenCalled();
    expect(log.warn).not.toHaveBeenCalled();
    expect(log.error).not.toHaveBeenCalled();
    expect(log.fatal).not.toHaveBeenCalled();
  });
});

šŸ‘ŗ Follow the "arrange, act, assert" pattern

Test Matchers

testSetup.js

import { matchers as jaypieMatchers } from "@jaypie/testkit";
import * as extendedMatchers from "jest-extended";
import { expect } from "vitest";

expect.extend(extendedMatchers);
expect.extend(jaypieMatchers);

test.spec.js

import { ConfigurationError } from "@jaypie/core";

const error = new ConfigurationError();
const json = error.json();
expect(error).toBeJaypieError();
expect(json).toBeJaypieError();

šŸ“– Reference

import { 
  LOG,
  jsonApiErrorSchema,
  jsonApiSchema,
  matchers,
  mockLogFactory,
  restoreLog,
  spyLog,
} from '@jaypie/testkit'

LOG

LOG constant provided by @jaypie/core for convenience

import { log } from "@jaypie/core";
import { LOG } from "@jaypie/testkit";

const libLogger = log.lib({ level: LOG.LEVEL.WARN, lib: "myLib" });

jsonApiErrorSchema

A JSON Schema validator for the JSON:API error schema. Powers the toBeJaypieError matcher (via toMatchSchema).

jsonApiSchema

A JSON Schema validator for the JSON:API data schema.

matchers

export default {
  toBeCalledAboveTrace,
  toBeCalledWithInitialParams,
  toBeClass,
  toBeJaypieError,
  toBeValidSchema: jsonSchemaMatchers.toBeValidSchema,
  toMatchBase64,
  toMatchJwt,
  toMatchMongoId,
  toMatchSchema: jsonSchemaMatchers.toMatchSchema,
  toMatchSignedCookie,
  toMatchUuid4,
  toMatchUuid5,
  toMatchUuid,
  toThrowBadGatewayError,
  toThrowBadRequestError,
  toThrowConfigurationError,
  toThrowForbiddenError,
  toThrowGatewayTimeoutError,
  toThrowInternalError,
  toThrowJaypieError,
  toThrowNotFoundError,
  toThrowUnauthorizedError,
  toThrowUnavailableError,
};

testSetup.js

import { matchers as jaypieMatchers } from "@jaypie/testkit";
import * as extendedMatchers from "jest-extended";
import { expect } from "vitest";

expect.extend(extendedMatchers);
expect.extend(jaypieMatchers);

expect(subject).toBeCalledAboveTrace()

import { log } from "@jaypie/core";

log.trace("Hello, World!");
expect(log).not.toBeCalledAboveTrace();

log.warn("Look out, World!");
expect(log).toBeCalledAboveTrace();

expect(subject).toBeJaypieError()

Validates instance objects:

try {
  throw new Error("Sorpresa!");
} catch (error) {
  expect(error).not.toBeJaypieError();
}

Validates plain old JSON:

expect({ errors: [ { status, title, detail } ] }).toBeJaypieError();

Jaypie errors, which are ProjectErrors, all have a .json() to convert

expect(subject).toBeValidSchema()

import { jsonApiErrorSchema, jsonApiSchema } from "@jaypie/testkit";

expect(jsonApiErrorSchema).toBeValidSchema();
expect(jsonApiSchema).toBeValidSchema();
expect({ project: "mayhem" }).not.toBeValidSchema();

From jest-json-schema toBeValidSchema.js (not documented in README)

expect(subject).toMatchSchema(schema)

import { jsonApiErrorSchema, jsonApiSchema } from "@jaypie/testkit";
import { ConfigurationError } from "@jaypie/core";

const error = new ConfigurationError();
const json = error.json();
expect(json).toMatchSchema(jsonApiErrorSchema);
expect(json).not.toMatchSchema(jsonApiSchema);

From jest-json-schema; see README

expect(subject).toMatch*() Regular Expression Matchers

Note: these regular expressions matchers so not verify the value is value, only that it matches the pattern (it "looks like" something). For example, expect("123e4567-e89b-12d3-a456-426614174000").toMatchUuid() will pass because the string matches a UUID pattern, even though it is not a valid UUID.

  • toMatchBase64
  • toMatchJwt
  • toMatchMongoId
  • toMatchSignedCookie
  • toMatchUuid4
  • toMatchUuid5
  • toMatchUuid

expect(subject).toThrowJaypieError()

import { ConfigurationError } from "@jaypie/core";

const error = new ConfigurationError();
expect(() => {
  throw error;
}).toThrowJaypieError();

Do not forget to await expect when passing async functions:

import { ConfigurationError } from "@jaypie/core";

const error = new ConfigurationError();
await expect(async () => {
  throw error;
}).toThrowJaypieError();

// Breaks and causes a false-positive because `expect` did not `await`
// expect(async () => {}).toThrowJaypieError();
// > Error: Expected function to throw a JaypieError, but it did not throw.

mockLogFactory()

Creates a mock of the log provided by @jaypie/core.

import { mockLogFactory } from "@jaypie/testkit";

const log = mockLogFactory();
log.warn("Danger");
expect(log.warn).toHaveBeenCalled();
expect(log.error).not.toHaveBeenCalled();

restoreLog(log)

Restores the log provided by @jaypie/core, commonly performed afterEach with spyLog in beforeEach. See example with spyLog.

spyLog(log)

Spies on the log provided by @jaypie/core, commonly performed beforeEach with restoreLog in afterEach. Not necessary when mocking the entire Jaypie module.

import { restoreLog, spyLog } from "@jaypie/testkit";
import { log } from "@jaypie/core";

beforeEach(() => {
  spyLog(log);
});
afterEach(() => {
  restoreLog(log);
  vi.clearAllMocks();
});

test("log", () => {
  log.warn("Danger");
  expect(log.warn).toHaveBeenCalled();
  expect(log.error).not.toHaveBeenCalled();
});

sqsTestRecords(message, message, ...) or sqsTestRecords([...])

Generates an event object for testing SQS Lambda functions with as many messages as provided. Note, test will accept more than ten messages, but AWS will only send ten at a time.

import { sqsTestRecords } from "@jaypie/testkit";

const event = sqsTestRecords(
  { MessageId: "1", Body: "Hello, World!" },
  { MessageId: "2", Body: "Goodbye, World!" }
);

🌠 Wishlist

  • matcher toBeHttpStatus
  • matcher toBeJaypieAny
  • matcher toBeJaypieData
  • matcher toBeJaypieDataObject
  • matcher toBeJaypieDataArray
  • ...@knowdev/jest

šŸ“ Changelog

DateVersionSummary
9/15/20241.0.29All errors exported as mocks
9/14/20241.0.28Matchers toThrowBadGatewayError, toThrowGatewayTimeoutError, toThrowUnavailableError
9/13/20241.0.27Matcher toBeCalledAboveTrace
7/16/20241.0.21Export Jaypie mock as default
3/20/20241.0.2Export LOG
3/16/20241.0.0Artists ship
3/15/20240.1.0Initial deploy
3/15/20240.0.1Initial commit

šŸ“œ License

Published by Finlayson Studio. All rights reserved

1.1.0

8 months ago

1.0.29

9 months ago

1.0.28

9 months ago

1.0.27

9 months ago

1.0.22

11 months ago

1.0.21

11 months ago

1.0.20

11 months ago

1.0.26

11 months ago

1.0.25

11 months ago

1.0.24

11 months ago

1.0.23

11 months ago

1.0.19

1 year ago

1.0.18

1 year ago

1.0.17

1 year ago

1.0.16

1 year ago

1.0.15

1 year ago

1.0.14

1 year ago

1.0.13

1 year ago

1.0.12

1 year ago

1.0.9

1 year ago

1.0.8

1 year ago

1.0.7

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.11

1 year ago

1.0.10

1 year ago

1.0.4

1 year ago

1.0.2

1 year ago

1.0.3

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago