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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@flink-app/test-utils

v0.12.1-alpha.45

Published

Test utils for Flink

Downloads

446

Readme

@flink-app/test-utils

A comprehensive testing utility library for Flink applications. This package provides helper functions and mocks to simplify writing tests for your Flink handlers, repositories, and plugins.

Features

  • HTTP testing utilities with automatic JSON handling
  • Mock request objects for unit testing handlers
  • No-op authentication plugin for testing
  • Type-safe test helpers
  • Support for query strings, headers, and authentication
  • Integration testing support
  • Works with Jasmine, Jest, and other testing frameworks

Installation

npm install --save-dev @flink-app/test-utils

HTTP Testing Utilities

The HTTP utilities allow you to make test requests to your running Flink app without needing external HTTP clients in most cases.

Setup

Initialize the test utilities with your Flink app instance:

import { FlinkApp } from "@flink-app/flink";
import * as http from "@flink-app/test-utils";

describe("My API", () => {
  let app: FlinkApp<AppContext>;

  beforeAll(async () => {
    app = new FlinkApp<AppContext>({
      name: "Test App",
      port: 3001,
      // ... your configuration
    });
    await app.start();

    // Initialize test utilities
    http.init(app);
  });

  afterAll(async () => {
    await app.stop();
  });

  // Your tests here
});

GET Requests

import { get } from "@flink-app/test-utils";

it("should get all items", async () => {
  const response = await get("/items");

  expect(response.status).toBe(200);
  expect(response.data).toBeDefined();
  expect(response.data.items).toBeInstanceOf(Array);
});

POST Requests

import { post } from "@flink-app/test-utils";

it("should create a new item", async () => {
  const newItem = {
    name: "Test Item",
    description: "A test item",
  };

  const response = await post("/items", newItem);

  expect(response.status).toBe(200);
  expect(response.data.item._id).toBeDefined();
  expect(response.data.item.name).toBe("Test Item");
});

PUT Requests

import { put } from "@flink-app/test-utils";

it("should update an item", async () => {
  const updates = {
    name: "Updated Name",
  };

  const response = await put("/items/123", updates);

  expect(response.status).toBe(200);
  expect(response.data.item.name).toBe("Updated Name");
});

DELETE Requests

import { del } from "@flink-app/test-utils";

it("should delete an item", async () => {
  const response = await del("/items/123");

  expect(response.status).toBe(200);
  expect(response.success).toBe(true);
});

Request Options

All HTTP methods support an optional options object:

Query String Parameters

const response = await get("/items", {
  qs: {
    limit: "10",
    offset: "0",
    category: "electronics",
  },
});
// Requests: /items?limit=10&offset=0&category=electronics

Custom Headers

const response = await post("/items", newItem, {
  headers: {
    "X-Custom-Header": "value",
    "X-Request-ID": "12345",
  },
});

Authentication

The test utilities support automatic authentication using the app's auth plugin:

const user = {
  _id: "user123",
  username: "testuser",
  roles: ["admin"],
};

const response = await get("/admin/users", {
  user: user,
});
// Automatically adds: Authorization: Bearer <token>

Combining Options

const response = await post(
  "/items",
  { name: "New Item" },
  {
    qs: { draft: "true" },
    headers: { "X-Request-ID": "123" },
    user: currentUser,
  }
);

Mock Request Objects

For unit testing handlers without a running server:

mockReq

Create a mock FlinkRequest object:

import { mockReq } from "@flink-app/test-utils";

it("should handle request correctly", async () => {
  const req = mockReq({
    body: { name: "Test" },
    params: { id: "123" },
    query: { include: "details" },
    headers: { "content-type": "application/json" },
  });

  const result = await myHandler(req, ctx);

  expect(result.status).toBe(200);
});

Mock Request Properties

const req = mockReq<RequestBody, Params, Query>({
  body: { name: "Test" },      // Request body
  params: { id: "123" },        // URL parameters
  query: { page: "1" },         // Query string
  headers: {                    // HTTP headers
    "content-type": "application/json",
    "authorization": "Bearer token",
  },
});

// Mock request automatically provides:
// - req.get(headerName) method
// - JSON serialization of body/params
// - Default empty objects for omitted properties

Authentication Mocking

noOpAuthPlugin

A no-op authentication plugin that allows all requests:

import { noOpAuthPlugin } from "@flink-app/test-utils";

const app = new FlinkApp<AppContext>({
  name: "Test App",
  port: 3001,
  plugins: [],
});

// Set the no-op auth plugin for testing
app.auth = noOpAuthPlugin();

await app.start();

This plugin:

  • Always authenticates successfully
  • Returns a mock token: "mock-token"
  • Useful for testing handlers without setting up real authentication

Complete Testing Example

import { FlinkApp } from "@flink-app/flink";
import * as http from "@flink-app/test-utils";
import { mockReq, noOpAuthPlugin } from "@flink-app/test-utils";

describe("Todo API", () => {
  let app: FlinkApp<AppContext>;

  beforeAll(async () => {
    app = new FlinkApp<AppContext>({
      name: "Test App",
      port: 3001,
      mongo: {
        url: "mongodb://localhost:27017/test",
      },
    });

    app.auth = noOpAuthPlugin();
    await app.start();
    http.init(app);
  });

  afterAll(async () => {
    await app.stop();
  });

  describe("GET /todos", () => {
    it("should return all todos", async () => {
      const response = await http.get("/todos");

      expect(response.status).toBe(200);
      expect(response.data.todos).toBeInstanceOf(Array);
    });

    it("should filter todos by status", async () => {
      const response = await http.get("/todos", {
        qs: { status: "completed" },
      });

      expect(response.status).toBe(200);
      expect(response.data.todos.every((t) => t.status === "completed")).toBe(true);
    });
  });

  describe("POST /todos", () => {
    it("should create a new todo", async () => {
      const newTodo = {
        title: "Test Todo",
        description: "Test Description",
      };

      const response = await http.post("/todos", newTodo);

      expect(response.status).toBe(200);
      expect(response.data.todo._id).toBeDefined();
      expect(response.data.todo.title).toBe("Test Todo");
    });

    it("should require authentication", async () => {
      const response = await http.post("/todos", {
        title: "Test",
      });

      // With noOpAuthPlugin, this would pass
      // With real auth, this would return 401
      expect(response.status).toBe(200);
    });

    it("should validate required fields", async () => {
      const response = await http.post("/todos", {});

      expect(response.status).toBe(400);
      expect(response.error).toBe("validation_error");
    });
  });

  describe("PUT /todos/:id", () => {
    let todoId: string;

    beforeEach(async () => {
      const created = await http.post("/todos", {
        title: "Todo to Update",
      });
      todoId = created.data.todo._id;
    });

    it("should update a todo", async () => {
      const response = await http.put(`/todos/${todoId}`, {
        title: "Updated Title",
        status: "completed",
      });

      expect(response.status).toBe(200);
      expect(response.data.todo.title).toBe("Updated Title");
    });
  });

  describe("DELETE /todos/:id", () => {
    it("should delete a todo", async () => {
      const created = await http.post("/todos", {
        title: "Todo to Delete",
      });

      const response = await http.del(`/todos/${created.data.todo._id}`);

      expect(response.status).toBe(200);
      expect(response.success).toBe(true);
    });
  });
});

Unit Testing Handlers

Test individual handlers without a running server:

import { mockReq } from "@flink-app/test-utils";
import GetTodoById from "./handlers/GetTodoById";

describe("GetTodoById Handler", () => {
  let mockContext: AppContext;

  beforeEach(() => {
    mockContext = {
      repos: {
        todoRepo: {
          findById: jasmine.createSpy("findById"),
        },
      },
    } as any;
  });

  it("should return todo when found", async () => {
    const mockTodo = {
      _id: "123",
      title: "Test Todo",
      status: "active",
    };

    (mockContext.repos.todoRepo.findById as any).and.returnValue(
      Promise.resolve(mockTodo)
    );

    const req = mockReq({
      params: { id: "123" },
    });

    const result = await GetTodoById(req, mockContext);

    expect(result.status).toBe(200);
    expect(result.data.todo).toEqual(mockTodo);
    expect(mockContext.repos.todoRepo.findById).toHaveBeenCalledWith("123");
  });

  it("should return 404 when not found", async () => {
    (mockContext.repos.todoRepo.findById as any).and.returnValue(
      Promise.resolve(null)
    );

    const req = mockReq({
      params: { id: "999" },
    });

    const result = await GetTodoById(req, mockContext);

    expect(result.status).toBe(404);
    expect(result.error).toBe("not_found");
  });
});

Response Type

All HTTP methods return a FlinkResponse object:

interface FlinkResponse<T> {
  status: number;           // HTTP status code
  success?: boolean;        // Success flag (if present in response)
  data?: T;                // Response data (if present)
  error?: string;          // Error code (if error occurred)
  message?: string;        // Error message (if error occurred)
  // ... any other fields from the response
}

TypeScript Support

The test utilities are fully typed:

interface CreateTodoRequest {
  title: string;
  description?: string;
}

interface CreateTodoResponse {
  todo: {
    _id: string;
    title: string;
    description?: string;
    createdAt: string;
  };
}

const response = await post<CreateTodoRequest, CreateTodoResponse>(
  "/todos",
  { title: "New Todo" }
);

// response.data is typed as CreateTodoResponse
expect(response.data?.todo._id).toBeDefined();

Best Practices

  1. Initialize once: Call http.init(app) once in beforeAll, not before each test
  2. Clean up: Always call app.stop() in afterAll
  3. Use separate database: Use a test database, not your production database
  4. Reset data: Clear test data between tests if needed
  5. Mock external services: Use mocks for external API calls
  6. Test error cases: Don't just test happy paths
  7. Use authentication: Test both authenticated and unauthenticated scenarios

Working with Different Test Frameworks

Jasmine

describe("My tests", () => {
  let app: FlinkApp<AppContext>;

  beforeAll(async () => {
    app = createTestApp();
    await app.start();
    http.init(app);
  });

  afterAll(async () => {
    await app.stop();
  });

  it("should work", async () => {
    const response = await http.get("/endpoint");
    expect(response.status).toBe(200);
  });
});

Jest

describe("My tests", () => {
  let app: FlinkApp<AppContext>;

  beforeAll(async () => {
    app = createTestApp();
    await app.start();
    http.init(app);
  });

  afterAll(async () => {
    await app.stop();
  });

  test("should work", async () => {
    const response = await http.get("/endpoint");
    expect(response.status).toBe(200);
  });
});

Troubleshooting

Port Already in Use

Use a different port for testing:

const app = new FlinkApp<AppContext>({
  port: 3001, // Different from dev/prod port
  // ...
});

Tests Hanging

Make sure to call app.stop() in afterAll:

afterAll(async () => {
  await app.stop();
});

Authentication Errors

Use noOpAuthPlugin() for testing or provide valid user objects:

app.auth = noOpAuthPlugin();

// OR

const response = await http.get("/endpoint", {
  user: { _id: "123", roles: ["admin"] },
});

License

MIT