Skip to content

Instantly share code, notes, and snippets.

@albscui
Last active August 21, 2025 23:08
Show Gist options
  • Save albscui/091e3a25ed63f57e50e5a73a17558d15 to your computer and use it in GitHub Desktop.
Save albscui/091e3a25ed63f57e50e5a73a17558d15 to your computer and use it in GitHub Desktop.
How to test in Go

Testing code with dependencies in Go

A list of patterns for stubbing out dependencies in unit tests.

Pattern 1: Simple stub struct

Problem: We have a dependency that we don't want to actually run in our unit tests.

Define an interface that abstracts this dependency, and implement a stub struct that implements this interface, then swap this into your struct at test time.

type MathSolver interface {
    Resolve(ctx context.Context, expression string) (float64, error)
}

type Processor struct {
    Solver MathSolver 
}

func (p Processor) ProcessExpression(ctx context.Context, r io.Reader) (float64, error) {
    // calls p.Solver.Resolve()
}

Create a stub struct that implements the MathSolver interface.

type MathSolverStub struct {}

func (ms MathSolverStub) Resolve(ctx context.Context, expression string) (float64, error) {
    // stub implementation
}

In our test we use the stub instead of the real thing.

func TestProcessorProcessExpression(t *testing.T) {
    p := Processor{MathSolverStub{}}
    // call p.ProcessExpression()
}

Pattern 2: Embed an interface into the stub

Problem: You have a wide interface, and its annoying to implement every method for the stub.

type Entities interface {
    GetUser(id string) (User, error)
    GetPets(userID string) ([]Pet, error)
    GetChildren(userID string) ([]Person, error)
    GetFriends(userID string) ([]Person, error)
    SaveUser(user User) error
}
type Logic struct {
    Entities Entities
}

func (l Logic) GetPetNames(userId string) ([]string, error) {
    // calls l.Entities.GetPets
}

Rather than creating a stub that implements every single method on Entities just to test GetPets, you can write a stub struct that only implements the method you need:

type GetPetNamesStub struct {
    Entities
}

func (ps GetPetNamesStub) GetPets(userId string) ([]Pet, error) {}

By embedding the Entities in GetPetNamesStub, it automatically satisfies the interface.

In our test make sure to only call methods that are implemented, otherwise you get a runtime error. This is one of the weaknesses of this approach.

func TestLogicGetPetNames(t *testing.T) {
    l := Logic{GetPetNamesStub{}}
    // only call l.GetPetNames
}

Pattern 3: Stub struct that proxies method calls to function fields

Problem: You want more granular control over which parts of the interface to stub out versus which parts to keep.

Lets keep using the Entities interface defined above.

For each method defined on Entities, we define a function field with a matching signature on our stub struct. We then make EntitiesStub implement the Entities interface by defining the methods. In each method, we involve the associated function field.

type EntitiesStub struct {
    getUser     func(id string) (User, error)
    getPets     func(userID string) ([]Pet, error)
    getChildren func(userID string) ([]Person, error)
    getFriends  func(userID string) ([]Person, error)
    saveUser    func(user User) error
}

func (es EntitiesStub) GetUser(id string) (User, error) {
    return es.getUser(id)
}

func (es EntitiesStub) GetPets(userID string) ([]Pet, error) {
    return es.getPets(userID)
}

In our test:

func TestLogicGetPetNames(t *testing.T) {
	tests := []struct {
		name     string
		getPets  func(userID string) ([]Pet, error)
		userID   string
		petNames []string
		errMsg   string
	}{
		{
			name: "case1",
			getPets: func(userID string) ([]Pet, error) {
				return []Pet{{Name: "Bubbles"}}, nil
			},
			userID:   "1",
			petNames: []string{"Bubbles"},
			errMsg:   ""
		},
		{
			name: "case2",
			getPets: func(userID string) ([]Pet, error) {
				return nil, errors.New("invalid id: 3")
			},
			userID: "3",
			petNames: nil,
			errMsg: "invalid id: 3"
		},
	}
	l := Logic{}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			// update the function field with the test case's version
			l.Entities = EntitiesStub{getPets: test.getPets}
			// l.GetPetNames()
		})
	}
}

Stubs vs Mocks

Stubs and Mocks are different concepts, but often used interchangeably. A stub returns a fake value for a given input. A mock validates that a set of calls happen in the expected order with the expected inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment