How to Test Supermemory Code: Complete Vitest Testing Guide
Supermemory ships with a modular, type-safe Vitest test suite that mocks HTTP calls and environment variables to validate memory injection, caching, and conversation persistence without requiring a live server.
The supermemoryai/supermemory repository uses Vitest to ensure every layer of the memory system—from the withSupermemory wrapper to the Mastra processors—functions correctly. Understanding how to test Supermemory code allows you to verify that memory transformations, context injections, and conversation saves work as intended before submitting changes.
Test Architecture Overview
Supermemory organizes tests into distinct layers that mirror the package structure. Each layer targets specific functionality while maintaining isolation through mocked dependencies.
Wrapper Layer Tests (withSupermemory)
Tests in packages/tools/test/with-supermemory/unit.test.ts validate that the wrapper creates a model which injects memories into prompts, persists responses when configured, and throws descriptive errors when the API key is missing. These tests mock globalThis.fetch using vi.fn() and verify that process.env.SUPERMEMORY_API_KEY is correctly consumed.
Processor Tests (Mastra)
The Mastra-specific suite in packages/tools/test/mastra/unit.test.ts exercises SupermemoryInputProcessor and SupermemoryOutputProcessor. These tests ensure that processors call the Supermemory HTTP API, cache results in MemoryCache, and handle error responses gracefully without crashing the inference pipeline.
Middleware Tests (transformParamsWithMemory)
The core memory-search flow—including query building, API invocation, and prompt injection—is tested via the transformParamsWithMemory function. Tests verify that the returned prompt array contains the injected memory string and that MemoryCache entries are populated with the expected keys.
Conversation Client Tests
Integration tests for conversation persistence validate the addConversation flow. When addMemory: "always" is set and a conversationId is supplied, tests confirm that the request body sent to the Supermemory endpoint contains the correct conversation metadata.
Setting Up the Test Environment
All Supermemory tests are self-contained and do not rely on external files or a running server. To prepare the environment:
- Install dependencies using Bun.
- Set a dummy API key in
process.env.SUPERMEMORY_API_KEY. - Replace
globalThis.fetchwith a Vitest mock (vi.fn()) that returns canned JSON responses.
This setup guarantees deterministic CI runs defined in .github/workflows/ci.yml.
Unit Testing the withSupermemory Wrapper
The following example from packages/tools/test/with-supermemory/unit.test.ts demonstrates how to test the Vercel AI SDK wrapper.
// packages/tools/test/with-supermemory/unit.test.ts
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"
import { withSupermemory } from "../../src/vercel"
import { openai } from "@ai-sdk/openai"
describe("withSupermemory wrapper", () => {
const TEST_CONFIG = {
apiKey: "test-api-key",
containerTag: "test-container",
}
let fetchMock: ReturnType<typeof vi.fn>
let originalEnv: string | undefined
let originalFetch: typeof globalThis.fetch
beforeEach(() => {
originalEnv = process.env.SUPERMEMORY_API_KEY
process.env.SUPERMEMORY_API_KEY = TEST_CONFIG.apiKey
originalFetch = globalThis.fetch
fetchMock = vi.fn()
globalThis.fetch = fetchMock as any
})
afterEach(() => {
if (originalEnv) process.env.SUPERMEMORY_API_KEY = originalEnv
else delete process.env.SUPERMEMORY_API_KEY
globalThis.fetch = originalFetch
})
it("injects memories and returns a wrapped model", async () => {
// Mock the Supermemory profile endpoint
fetchMock.mockResolvedValue({
ok: true,
json: () =>
Promise.resolve({
profile: {
static: [{ memory: "User likes TypeScript" }],
dynamic: [{ memory: "Recent interest in AI" }],
},
}),
})
const model = withSupermemory(openai("gpt-4"), TEST_CONFIG.containerTag, {
mode: "profile",
})
const result = await model.doGenerate({
prompt: [{ role: "user", content: "What languages do I prefer?" }],
})
// The mocked response is injected as a system prompt
const systemMsg = result.prompt?.[0]
expect(systemMsg?.role).toBe("system")
expect(systemMsg?.content).toContain("User likes TypeScript")
expect(fetchMock).toHaveBeenCalledTimes(1)
})
})
Key implementation details:
- Lines 16-31 preserve the original environment and fetch implementation before mocking.
- The mock returns a profile containing static and dynamic memories.
- Assertions verify that the memory content appears in the system prompt role.
Testing Memory Caching and Processors
To validate that the MemoryCache prevents redundant API calls, test the processors directly.
// packages/tools/test/mastra/unit.test.ts (excerpt)
it("caches memories on second call with same message", async () => {
fetchMock.mockResolvedValue({
ok: true,
json: () => Promise.resolve(createMockProfileResponse(["Cached memory"])),
})
const processor = new SupermemoryInputProcessor("test-tag", {
apiKey: "test-key",
mode: "profile",
})
const messages = [createMessage("user", "Hello")]
const args1 = {
messages,
systemMessages: [],
messageList: createMockMessageList(),
abort: vi.fn() as never,
retryCount: 0,
}
await processor.processInput(args1) // first call → fetch
expect(fetchMock).toHaveBeenCalledTimes(1)
const args2 = { ...args1, messageList: createMockMessageList() }
await processor.processInput(args2) // second call → cached
expect(fetchMock).toHaveBeenCalledTimes(1) // no extra fetch
})
Caching validation:
- The first call to
processInputtriggers a network request. - The second call with identical parameters reuses the cached value.
fetchMockreports exactly one invocation, confirming cache efficiency.
Integration Testing with Conversations
When testing conversation persistence, verify that the SupermemoryOutputProcessor correctly formats payloads for the /v4/conversations endpoint.
// packages/tools/test/mastra/integration.test.ts (excerpt)
it("saves a conversation when addMemory is always", async () => {
const conversationId = `test-conv-${Date.now()}`
fetchMock.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: "mem-123", status: "created" }),
})
const processor = new SupermemoryOutputProcessor("test-tag", {
apiKey: "test-key",
addMemory: "always",
threadId: conversationId,
})
const args = {
messages: [
createMessage("user", "Hello"),
createMessage("assistant", "Hi!"),
],
messageList: createMockMessageList(),
abort: vi.fn() as never,
retryCount: 0,
}
await processor.processOutputResult(args)
expect(fetchMock).toHaveBeenCalledTimes(1)
const body = JSON.parse(fetchMock.mock.calls[0][1].body)
expect(body.conversationId).toBe(conversationId)
})
Payload verification:
- The test constructs a realistic message history with user and assistant roles.
- After processing, the request body is parsed to confirm
conversationIdmatches the input.
Running Tests Locally and in CI
Execute the full test suite using Bun and Turbo.
# Install dependencies
bun install
# Run the full suite scoped to the tools package
bunx turbo run test --filter='@supermemory/tools'
The turbo command targets the @supermemory/tools package, which contains all unit and integration tests. The package.json defines the test script as vitest --testTimeout 100000, allowing sufficient time for async memory operations.
In CI, as defined in .github/workflows/ci.yml, type checking runs separately for @supermemory/ai-sdk and @supermemory/memory-graph, while functional tests execute via Vitest. This separation keeps the pipeline fast while ensuring comprehensive coverage.
Summary
- Supermemory tests are self-contained: They mock
fetchand environment variables to avoid external dependencies. - Vitest powers the suite: All tests run with
bunx turbo run test --filter='@supermemory/tools'. - Three layers are validated: Wrappers (
withSupermemory), processors (SupermemoryInputProcessor,SupermemoryOutputProcessor), and middleware (transformParamsWithMemory). - Caching is verified: Tests confirm that
MemoryCacheprevents duplicate API calls across identical prompts. - Conversation persistence is tested: Integration tests verify that
conversationIdand message lists reach the Supermemory endpoint whenaddMemory: "always"is configured.
Frequently Asked Questions
How do I mock the Supermemory API in my own tests?
Create a vi.fn() mock and assign it to globalThis.fetch. Return a resolved Response object with the JSON shape expected from the Supermemory API, including profile, searchResults, or conversation creation payloads. This technique is used in packages/tools/test/with-supermemory/unit.test.ts to test memory injection without network calls.
Why does the test suite use Bun instead of Node.js?
Supermemory uses Bun for its faster package resolution and built-in TypeScript support. The test commands (bun install and bunx turbo run test) leverage Bun's performance characteristics while remaining compatible with Vitest, which handles the actual test execution and assertions.
What is the difference between unit and integration tests in Supermemory?
Unit tests mock all external HTTP calls and validate logic such as prompt transformation and caching in SupermemoryInputProcessor. Integration tests, located in packages/tools/test/mastra/integration.test.ts, simulate full conversation flows and verify that the request payloads sent to the /v4/conversations endpoint contain valid conversationId values and message structures.
How does the CI pipeline handle test timeouts?
The package.json in the tools package specifies --testTimeout 100000 (100 seconds) to accommodate asynchronous memory operations. The GitHub Actions workflow runs type checking separately from the Vitest suite to ensure fast feedback on type errors while allowing the functional tests sufficient time to complete.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →