elasticapm-decorators
Version:
ElasticAPM Decorators built for Typescript methods to assess their performance
149 lines (134 loc) • 3.73 kB
text/typescript
import apm, { type Span } from "elastic-apm-node";
import { beforeEach, describe, expect, it, type Mock, vi } from "vitest";
import { ElasticSpan } from "./span";
vi.spyOn(apm, "isStarted").mockReturnValue(false);
vi.spyOn(apm, "startSpan").mockReturnValue({
name: "mock-span",
type: "custom",
subtype: "sub",
setOutcome: vi.fn(),
addLabels: vi.fn(),
end: vi.fn(),
} as unknown as Span);
class TestNotStartedClass {
("test-span", "custom", "sub")
async testMethod(val: string) {
if (val === "error") throw new Error("Test error");
return `Hello ${val}`;
}
("multi-param-span", "custom", "sub")
processOrder(
orderId: string,
customer: { id: string; name: string },
items: Array<{ sku: string; quantity: number }>,
options: { express: boolean; notes?: string },
) {
return {
orderId,
customer,
items,
options,
};
}
}
vi.spyOn(apm, "isStarted").mockReturnValue(true);
class TestClass {
("test-span", "custom", "sub")
async testMethod(val: string) {
if (val === "error") throw new Error("Test error");
return `Hello ${val}`;
}
("multi-param-span", "custom", "sub")
processOrder(
orderId: string,
customer: { id: string; name: string },
items: Array<{ sku: string; quantity: number }>,
options: { express: boolean; notes?: string },
) {
return {
orderId,
customer,
items,
options,
};
}
}
describe("ElasticSpan", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(apm, "isStarted").mockReturnValue(true);
vi.spyOn(apm, "startSpan").mockImplementation(
() =>
({
setOutcome: vi.fn(),
addLabels: vi.fn(),
end: vi.fn(),
}) as unknown as Span,
);
});
it("should skip span logic if apm is not started", async () => {
const instance = new TestNotStartedClass();
const result = await instance.testMethod("world");
expect(result).toBe("Hello world");
});
it("starts a span and returns result", async () => {
const setOutcome = vi.fn();
const addLabels = vi.fn();
const end = vi.fn();
(apm.startSpan as Mock).mockReturnValue({
setOutcome,
addLabels,
end,
});
const instance = new TestClass();
const result = await instance.testMethod("world");
expect(result).toBe("Hello world");
expect(apm.startSpan).toHaveBeenCalledWith("test-span", "custom", "sub");
expect(setOutcome).toHaveBeenCalledWith("success");
expect(addLabels).toHaveBeenCalled();
expect(end).toHaveBeenCalled();
});
it("handles errors and sets span outcome", async () => {
const setOutcome = vi.fn();
const addLabels = vi.fn();
const end = vi.fn();
(apm.startSpan as Mock).mockReturnValue({
setOutcome,
addLabels,
end,
});
const instance = new TestClass();
await expect(instance.testMethod("error")).rejects.toThrow("Test error");
expect(setOutcome).toHaveBeenCalledWith("failure");
expect(addLabels).toHaveBeenCalled();
expect(end).toHaveBeenCalled();
});
it("if span is null, proceeds without span operations", async () => {
(apm.startSpan as Mock).mockReturnValue(null);
const instance = new TestClass();
const result = await instance.testMethod("world");
expect(result).toBe("Hello world");
});
it("handles methods with multiple parameters", async () => {
const instance = new TestClass();
const orderId = "ORDER-123";
const customer = { id: "CUST-1", name: "Alice" };
const items = [
{ sku: "SKU-001", quantity: 2 },
{ sku: "SKU-002", quantity: 1 },
];
const options = { express: true, notes: "Leave at door" };
const result = await instance.processOrder(
orderId,
customer,
items,
options,
);
expect(result).toEqual({
orderId,
customer,
items,
options,
});
});
});