typestar

store_test.go in Go

A whole test file: table tests, subtests, a fake, a benchmark and a fuzz target.

package store

import (
    "errors"
    "fmt"
    "strings"
    "testing"
)

// ErrBadStars means the value was outside zero to three.
var ErrBadStars = errors.New("stars out of range")

// Saver persists one run. Tests supply their own.
type Saver interface {
    Save(lang string, stars int) error
}

// Record validates a run and hands it to the saver.
func Record(s Saver, lang string, stars int) error {
    if stars < 0 || stars > 3 {
        return fmt.Errorf("record %s: %w", lang, ErrBadStars)
    }
    return s.Save(strings.ToLower(strings.TrimSpace(lang)), stars)
}

type fakeSaver struct {
    calls []string
    err   error
}

func (f *fakeSaver) Save(lang string, stars int) error {
    f.calls = append(f.calls, fmt.Sprintf("%s=%d", lang, stars))
    return f.err
}

func TestRecordValidates(t *testing.T) {
    cases := []struct {
        name    string
        lang    string
        stars   int
        wantErr error
        wantCall string
    }{
        {"clean", " Go ", 3, nil, "go=3"},
        {"zero is fine", "rust", 0, nil, "rust=0"},
        {"too many", "sql", 9, ErrBadStars, ""},
    }

    for _, c := range cases {
        t.Run(c.name, func(t *testing.T) {
            fake := &fakeSaver{}
            err := Record(fake, c.lang, c.stars)

            if !errors.Is(err, c.wantErr) {
                t.Fatalf("err = %v, want %v", err, c.wantErr)
            }
            if c.wantCall == "" {
                if len(fake.calls) != 0 {
                    t.Errorf("saved despite the error: %v", fake.calls)
                }
                return
            }
            if len(fake.calls) != 1 || fake.calls[0] != c.wantCall {
                t.Errorf("calls = %v, want [%s]", fake.calls, c.wantCall)
            }
        })
    }
}

func TestRecordPropagatesSaverError(t *testing.T) {
    boom := errors.New("disk full")
    if err := Record(&fakeSaver{err: boom}, "go", 2); !errors.Is(err, boom) {
        t.Errorf("err = %v, want %v", err, boom)
    }
}

func BenchmarkRecord(b *testing.B) {
    fake := &fakeSaver{}
    b.ReportAllocs()
    for b.Loop() {
        fake.calls = fake.calls[:0]
        _ = Record(fake, " Go ", 3)
    }
}

func FuzzRecord(f *testing.F) {
    f.Add("go", 3)

    f.Fuzz(func(t *testing.T, lang string, stars int) {
        fake := &fakeSaver{}
        err := Record(fake, lang, stars)
        inRange := stars >= 0 && stars <= 3
        if inRange && err != nil {
            t.Errorf("Record(%q, %d) = %v, want nil", lang, stars, err)
        }
        if !inRange && !errors.Is(err, ErrBadStars) {
            t.Errorf("Record(%q, %d) = %v, want ErrBadStars", lang, stars, err)
        }
    })
}

How it works

  1. The fake implements the one-method interface the code depends on.
  2. Cases live in a slice, so adding one is a single line.
  3. Errors are compared with errors.Is, never by message.

Keywords and builtins used here

The run, in numbers

Lines
100
Characters to type
2177
Tokens
570
Three-star pace
110 tpm

At the three-star pace of 110 tokens a minute, this run takes about 311 seconds.

Type this snippet

Step 1 of 1 in Encore, step 13 of 13 in Testing.

← Previous