Go

How do you mock dependencies in Go without a mocking framework? Where should the interface live?

Question 384MediumGo 1.22 to 1.25

Go's implicit interface satisfaction lets the consumer define a small interface describing only what it needs; production code passes the real type, tests pass a hand-written fake. "Accept interfaces, return structs."

// in package billing (the consumer)
type PaymentGateway interface {
    Charge(ctx context.Context, customerID string, cents int64) (string, error)
}

type Service struct{ gw PaymentGateway }

func (s *Service) Checkout(ctx context.Context, id string, cents int64) error {
    if cents <= 0 {
        return errors.New("invalid amount")
    }
    _, err := s.gw.Charge(ctx, id, cents)
    return err
}

// in billing_test.go
type fakeGateway struct {
    calls []int64
    err   error
}

func (f *fakeGateway) Charge(_ context.Context, _ string, cents int64) (string, error) {
    f.calls = append(f.calls, cents)
    return "txn_1", f.err
}

func TestCheckoutPropagatesError(t *testing.T) {
    fg := &fakeGateway{err: errors.New("declined")}
    s := &Service{gw: fg}
    if err := s.Checkout(t.Context(), "c1", 500); err == nil {
        t.Fatal("want error")
    }
    if len(fg.calls) != 1 || fg.calls[0] != 500 {
        t.Fatalf("calls = %v", fg.calls)
    }
}

Interviewer is looking for: small interfaces (1–3 methods) defined at the point of use, not a giant interface exported next to the implementation "for mocking." Prefer fakes (working in-memory implementations) over strict call-order mocks — they're less brittle. Tools like gomock/mockery are fine for big surfaces, but over-mocking tests implementation rather than behavior. For time, inject a func() time.Time or use synctest.

More on Performance, Profiling & Testing

All 38 Performance, Profiling & Testing questions