---
title: "Unit Tests"
description: "Unit testing patterns for code using the TraceRTM Go SDK"
---

# Unit Tests

Best practices and patterns for writing unit tests for code that uses the TraceRTM Go SDK.

## Test Structure

### Basic Test File

```go
// requirements_test.go
package requirements

import (
    "context"
    "testing"

    "github.com/tracertm/tracertm-go"
)

func TestGetRequirement(t *testing.T) {
    ctx := context.Background()

    mock := &MockItemsService{
        GetFunc: func(ctx context.Context, id string) (*tracertm.Item, error) {
            return &tracertm.Item{
                ID:    id,
                Key:   "REQ-1",
                Title: "User Authentication",
                Type:  "requirement",
            }, nil
        },
    }

    svc := NewRequirementsService(mock)
    req, err := svc.GetRequirement(ctx, "item_123")

    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    if req.Key != "REQ-1" {
        t.Errorf("expected key REQ-1, got %s", req.Key)
    }
}
```

### Test Suite Organization

```go
func TestRequirementsService(t *testing.T) {
    t.Run("Get", func(t *testing.T) {
        t.Run("returns item when found", testGetRequirementFound)
        t.Run("returns error when not found", testGetRequirementNotFound)
        t.Run("returns error on context timeout", testGetRequirementTimeout)
    })

    t.Run("List", func(t *testing.T) {
        t.Run("returns all requirements", testListAllRequirements)
        t.Run("filters by status", testListFilterByStatus)
        t.Run("handles pagination", testListPagination)
    })

    t.Run("Create", func(t *testing.T) {
        t.Run("creates valid requirement", testCreateValidRequirement)
        t.Run("validates input", testCreateValidation)
    })
}

func testGetRequirementFound(t *testing.T) {
    // Test implementation
}

func testGetRequirementNotFound(t *testing.T) {
    // Test implementation
}
```

---

## Table-Driven Tests

### Basic Table Tests

```go
func TestValidateInput(t *testing.T) {
    tests := []struct {
        name    string
        input   *tracertm.CreateItemInput
        wantErr bool
    }{
        {
            name: "valid input",
            input: &tracertm.CreateItemInput{
                Type:  "requirement",
                Title: "Valid Requirement",
            },
            wantErr: false,
        },
        {
            name: "missing type",
            input: &tracertm.CreateItemInput{
                Title: "No Type",
            },
            wantErr: true,
        },
        {
            name: "missing title",
            input: &tracertm.CreateItemInput{
                Type: "requirement",
            },
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := validateInput(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("validateInput() error = %v, wantErr %v", err, tt.wantErr)
            }
        })
    }
}
```

### Table Tests with Setup

```go
func TestProcessItem(t *testing.T) {
    tests := []struct {
        name      string
        setupMock func(*MockItemsService)
        itemID    string
        want      *ProcessResult
        wantErr   bool
    }{
        {
            name: "process requirement",
            setupMock: func(m *MockItemsService) {
                m.GetFunc = func(ctx context.Context, id string) (*tracertm.Item, error) {
                    return &tracertm.Item{
                        ID:   id,
                        Type: "requirement",
                    }, nil
                }
            },
            itemID:  "item_123",
            want:    &ProcessResult{Type: "requirement", Processed: true},
            wantErr: false,
        },
        {
            name: "item not found",
            setupMock: func(m *MockItemsService) {
                m.GetFunc = func(ctx context.Context, id string) (*tracertm.Item, error) {
                    return nil, &tracertm.NotFoundError{ResourceID: id}
                }
            },
            itemID:  "item_999",
            want:    nil,
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            mock := &MockItemsService{}
            tt.setupMock(mock)

            svc := NewProcessor(mock)
            got, err := svc.Process(context.Background(), tt.itemID)

            if (err != nil) != tt.wantErr {
                t.Errorf("Process() error = %v, wantErr %v", err, tt.wantErr)
                return
            }

            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("Process() = %v, want %v", got, tt.want)
            }
        })
    }
}
```

---

## Testing Async Operations

### Testing Goroutines

```go
func TestAsyncFetch(t *testing.T) {
    mock := &MockItemsService{
        GetFunc: func(ctx context.Context, id string) (*tracertm.Item, error) {
            time.Sleep(10 * time.Millisecond) // Simulate delay
            return &tracertm.Item{ID: id}, nil
        },
    }

    ctx := context.Background()
    resultCh := make(chan *tracertm.Item, 1)
    errCh := make(chan error, 1)

    go func() {
        item, err := mock.Get(ctx, "item_123")
        if err != nil {
            errCh <- err
            return
        }
        resultCh <- item
    }()

    select {
    case item := <-resultCh:
        if item.ID != "item_123" {
            t.Errorf("expected ID item_123, got %s", item.ID)
        }
    case err := <-errCh:
        t.Fatalf("unexpected error: %v", err)
    case <-time.After(time.Second):
        t.Fatal("test timeout")
    }
}
```

### Testing with WaitGroup

```go
func TestBatchProcess(t *testing.T) {
    var processed []string
    var mu sync.Mutex

    mock := &MockItemsService{
        GetFunc: func(ctx context.Context, id string) (*tracertm.Item, error) {
            mu.Lock()
            processed = append(processed, id)
            mu.Unlock()
            return &tracertm.Item{ID: id}, nil
        },
    }

    ctx := context.Background()
    ids := []string{"item_1", "item_2", "item_3"}

    var wg sync.WaitGroup
    for _, id := range ids {
        wg.Add(1)
        go func(itemID string) {
            defer wg.Done()
            mock.Get(ctx, itemID)
        }(id)
    }

    wg.Wait()

    if len(processed) != 3 {
        t.Errorf("expected 3 processed items, got %d", len(processed))
    }
}
```

---

## Testing Error Cases

### Error Type Testing

```go
func TestErrorTypes(t *testing.T) {
    tests := []struct {
        name    string
        err     error
        checkFn func(error) bool
    }{
        {
            name: "not found error",
            err:  &tracertm.NotFoundError{ResourceType: "item", ResourceID: "123"},
            checkFn: func(err error) bool {
                var e *tracertm.NotFoundError
                return errors.As(err, &e)
            },
        },
        {
            name: "validation error",
            err:  &tracertm.ValidationError{Message: "invalid"},
            checkFn: func(err error) bool {
                var e *tracertm.ValidationError
                return errors.As(err, &e)
            },
        },
        {
            name: "rate limit error",
            err:  &tracertm.RateLimitError{RetryAfter: 60},
            checkFn: func(err error) bool {
                var e *tracertm.RateLimitError
                return errors.As(err, &e)
            },
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if !tt.checkFn(tt.err) {
                t.Errorf("error check failed for %s", tt.name)
            }
        })
    }
}
```

### Testing Error Messages

```go
func TestErrorMessages(t *testing.T) {
    tests := []struct {
        name        string
        err         error
        wantMessage string
    }{
        {
            name:        "not found message",
            err:         &tracertm.NotFoundError{ResourceType: "item", ResourceID: "123"},
            wantMessage: "item not found: 123",
        },
        {
            name: "validation message",
            err: &tracertm.ValidationError{
                Message: "validation failed",
                Fields: []tracertm.FieldError{
                    {Field: "title", Message: "required"},
                },
            },
            wantMessage: "validation failed",
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if !strings.Contains(tt.err.Error(), tt.wantMessage) {
                t.Errorf("error message %q should contain %q", tt.err.Error(), tt.wantMessage)
            }
        })
    }
}
```

---

## Test Fixtures

### Creating Test Data

```go
// fixtures_test.go

func newTestItem(id string) *tracertm.Item {
    return &tracertm.Item{
        ID:          id,
        ProjectID:   "proj_test",
        Key:         fmt.Sprintf("REQ-%s", id),
        Type:        "requirement",
        Title:       fmt.Sprintf("Test Item %s", id),
        Description: "Test description",
        Status:      "draft",
        Priority:    "medium",
        CreatedAt:   time.Now(),
        UpdatedAt:   time.Now(),
    }
}

func newTestProject(id string) *tracertm.Project {
    return &tracertm.Project{
        ID:          id,
        Name:        fmt.Sprintf("Test Project %s", id),
        Key:         fmt.Sprintf("TP%s", id),
        Description: "Test project description",
        CreatedAt:   time.Now(),
        UpdatedAt:   time.Now(),
    }
}

func newTestItemList(count int) *tracertm.ItemList {
    items := make([]*tracertm.Item, count)
    for i := 0; i < count; i++ {
        items[i] = newTestItem(fmt.Sprintf("item_%d", i))
    }
    return &tracertm.ItemList{
        Data:    items,
        Total:   count,
        Limit:   count,
        Offset:  0,
        HasMore: false,
    }
}
```

### Using Fixtures

```go
func TestWithFixtures(t *testing.T) {
    items := newTestItemList(5)

    mock := &MockItemsService{
        ListFunc: func(ctx context.Context, projectID string, opts *tracertm.ListItemsOptions) (*tracertm.ItemList, error) {
            return items, nil
        },
    }

    svc := NewService(mock)
    result, err := svc.ListRequirements(context.Background(), "proj_test")

    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    if len(result) != 5 {
        t.Errorf("expected 5 items, got %d", len(result))
    }
}
```

---

## Assertion Helpers

```go
// assertions_test.go

func assertEqual[T comparable](t *testing.T, got, want T, msg string) {
    t.Helper()
    if got != want {
        t.Errorf("%s: got %v, want %v", msg, got, want)
    }
}

func assertNil(t *testing.T, value interface{}, msg string) {
    t.Helper()
    if value != nil {
        t.Errorf("%s: expected nil, got %v", msg, value)
    }
}

func assertNotNil(t *testing.T, value interface{}, msg string) {
    t.Helper()
    if value == nil {
        t.Errorf("%s: expected non-nil value", msg)
    }
}

func assertError(t *testing.T, err error, msg string) {
    t.Helper()
    if err == nil {
        t.Errorf("%s: expected error, got nil", msg)
    }
}

func assertNoError(t *testing.T, err error, msg string) {
    t.Helper()
    if err != nil {
        t.Errorf("%s: unexpected error: %v", msg, err)
    }
}

func assertLen[T any](t *testing.T, slice []T, want int, msg string) {
    t.Helper()
    if len(slice) != want {
        t.Errorf("%s: len=%d, want %d", msg, len(slice), want)
    }
}
```

### Using Assertion Helpers

```go
func TestWithAssertions(t *testing.T) {
    mock := &MockItemsService{
        GetFunc: func(ctx context.Context, id string) (*tracertm.Item, error) {
            return newTestItem(id), nil
        },
    }

    item, err := mock.Get(context.Background(), "item_123")

    assertNoError(t, err, "Get item")
    assertNotNil(t, item, "item should not be nil")
    assertEqual(t, item.ID, "item_123", "item ID")
    assertEqual(t, item.Type, "requirement", "item type")
}
```

---

## Running Tests

### Run All Tests

```bash
go test ./...
```

### Run Specific Tests

```bash
# Run tests in a package
go test ./requirements/...

# Run specific test
go test -run TestGetRequirement ./...

# Run with verbose output
go test -v ./...

# Run with coverage
go test -cover ./...

# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
```

### Test Tags

```go
// +build unit

package requirements_test

func TestUnitOnly(t *testing.T) {
    // This test only runs with -tags=unit
}
```

```bash
go test -tags=unit ./...
```

---

## Related Documentation

- [Testing](/docs/api-reference/sdks/go/testing) - Testing overview
- [Mocking](/docs/api-reference/sdks/go/mocking) - Mock implementations
- [Error Handling](/docs/api-reference/sdks/go/error-handling) - Error types
