Guides

Testing Guide

This guide explains how to write unit and integration tests for applications using TableTheory.

  • Go: this document (mocks in pkg/mocks)
  • TypeScript: runtime-specific testing guide is staged alongside this document in the shared TableTheory subtree
  • Python: runtime-specific testing guide is staged alongside this document in the shared TableTheory subtree

Go unit testing with mocks and state-backed fakes

To write unit tests without connecting to DynamoDB, use the core.DB interface and the provided mocks.

1. Define Dependencies via Interface

Don’t depend on the concrete *tabletheory.DB struct. Use core.DB.

import "github.com/theory-cloud/tabletheory/v2/pkg/core"

type UserService struct {
    db core.DB
}

func NewUserService(db core.DB) *UserService {
    return &UserService{db: db}
}

2. Use Mocks in Tests

TableTheory provides mocks in the mocks package (or generate your own with mockery).

import (
    "testing"
    "github.com/stretchr/testify/mock"
    "github.com/theory-cloud/tabletheory/v2/pkg/mocks"
)

func TestCreateUser(t *testing.T) {
    // Setup Mocks
    mockDB := new(mocks.MockDB)
    mockQuery := new(mocks.MockQuery)

    // Expect Model() to be called, return mock query
    mockDB.On("Model", mock.Anything).Return(mockQuery)

    // Expect Create() to be called
    mockQuery.On("Create").Return(nil)

    // Test Service
    service := NewUserService(mockDB)
    err := service.CreateUser("john")

    // Assertions
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }
    mockDB.AssertExpectations(t)
}

3. Encryption + lifecycle determinism (Go)

If you use theorydb:"encrypted" fields or lifecycle tags, inject test doubles via session.Config:

import (
    "bytes"
    "testing"
    "time"

    "github.com/aws/aws-sdk-go-v2/service/kms"
    "github.com/theory-cloud/tabletheory/v2"
    "github.com/theory-cloud/tabletheory/v2/pkg/mocks"
    "github.com/theory-cloud/tabletheory/v2/pkg/session"
    "github.com/stretchr/testify/mock"
)

func TestEncryptedWrites(t *testing.T) {
    kmsMock := new(mocks.MockKMSClient)
    kmsMock.On("GenerateDataKey", mock.Anything, mock.Anything, mock.Anything).
        Return(&kms.GenerateDataKeyOutput{
            Plaintext:      bytes.Repeat([]byte{0x00}, 32),
            CiphertextBlob: []byte("edk"),
        }, nil)

    db, _ := tabletheory.New(session.Config{
        Region:         "us-east-1",
        KMSKeyARN:      "arn:aws:kms:us-east-1:111111111111:key/test",
        KMSClient:      kmsMock,
        EncryptionRand: bytes.NewReader(bytes.Repeat([]byte{0x01}, 64)),
        Now:            func() time.Time { return time.Unix(0, 0).UTC() },
    })

    _ = db
}

4. State-backed fake client (Go)

For consumer tests that should exercise TableTheory write/query behavior without Docker, construct a real DB over the deterministic in-memory fake:

import (
    "testing"

    "github.com/theory-cloud/tabletheory/v2"
    "github.com/theory-cloud/tabletheory/v2/pkg/session"
    "github.com/theory-cloud/tabletheory/v2/pkg/testing/fakedb"
)

func TestServiceWritesAndQueries(t *testing.T) {
    fake := fakedb.New()
    db, err := tabletheory.NewWithClient(session.Config{Region: "us-east-1"}, fake)
    if err != nil {
        t.Fatal(err)
    }

    if err := db.CreateTable(&User{}); err != nil {
        t.Fatal(err)
    }

    if err := db.Model(&User{PK: "USER#1", SK: "PROFILE", Email: "a@example.com"}).Create(); err != nil {
        t.Fatal(err)
    }

    // Exercise consumer code using db. Use fake.Items("users") for direct assertions when useful.
}

NewWithClient accepts any implementation of the public tabletheory.DynamoDBAPI seam. pkg/testing/fakedb honors TableTheory keys, conditional writes, optimistic-lock version increments, TTL attributes, batches, transactions, and basic query/scan filters. It is a deterministic local testing aid, not a DynamoDB replacement; behavior beyond the scenario-validated fake lane should still be covered with DynamoDB Local integration tests.

TypeScript unit testing

Use @theory-cloud/tabletheory-ts/testkit for a strict AWS SDK v3 send() mock and deterministic helpers:

import { PutItemCommand } from "@aws-sdk/client-dynamodb";
import { TheorydbClient } from "@theory-cloud/tabletheory-ts";
import {
  createMockDynamoDBClient,
  fixedNow,
} from "@theory-cloud/tabletheory-ts/testkit";

const mock = createMockDynamoDBClient();
mock.when(PutItemCommand, async () => ({}));

const db = new TheorydbClient(mock.client, {
  now: fixedNow("2026-01-16T00:00:00.000000000Z"),
});

For stateful write-then-query tests, use the TypeScript stateful fake:

import { TheorydbClient } from "@theory-cloud/tabletheory-ts";
import { createStatefulDynamoDBClient } from "@theory-cloud/tabletheory-ts/testkit";

const { client, fake } = createStatefulDynamoDBClient();
const db = new TheorydbClient(client);

// Register models, write through db, then query through db.
// fake.items("users") returns a deterministic snapshot for direct assertions.

Python unit testing

Use tabletheory_py.mocks for strict fakes and deterministic encryption nonces:

from tabletheory_py import Table
from tabletheory_py.mocks import FakeDynamoDBClient, FakeKmsClient

fake_ddb = FakeDynamoDBClient()
fake_kms = FakeKmsClient(plaintext_key=b"\x00" * 32, ciphertext_blob=b"edk")

table = Table(model, client=fake_ddb, kms_key_arn="arn:aws:kms:...", kms_client=fake_kms, rand_bytes=lambda n: b"\x01" * n)

For stateful Python tests that should exercise TableTheory query/write behavior without scripting every command:

from tabletheory_py import StatefulDynamoDBClient, Table

fake_ddb = StatefulDynamoDBClient()
table = Table(model, client=fake_ddb)

# table.put(...), table.query(...), table.update(...)
# fake_ddb.items("notes") returns a deterministic snapshot for direct assertions.

Integration Testing

For integration tests, connect to a real DynamoDB instance or DynamoDB Local.

TypeScript integration tests

make docker-up
npm --prefix ts run test:integration

Python integration tests

make docker-up
uv --directory py run pytest -q tests/integration

Go integration tests

func TestIntegration(t *testing.T) {
    // Connect to DynamoDB Local
    db, _ := tabletheory.New(session.Config{
        Endpoint: "http://localhost:8000",
        Region:   "us-east-1",
    })

    // Create Table
    db.CreateTable(&User{})

    // Run Test
    err := db.Model(&User{ID: "1"}).Create()
    if err != nil {
        t.Fatal(err)
    }
}