@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-utilsHTTP 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=electronicsCustom 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 propertiesAuthentication 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
- Initialize once: Call
http.init(app)once inbeforeAll, not before each test - Clean up: Always call
app.stop()inafterAll - Use separate database: Use a test database, not your production database
- Reset data: Clear test data between tests if needed
- Mock external services: Use mocks for external API calls
- Test error cases: Don't just test happy paths
- 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
