liveBook · Manning

Integration test

As explained in Learn Go with Pocket-Sized Projects

Integration testing evaluates interactions between multiple components or a component and an external dependency as an integrated system.

An integration test checks a sequence of operations through components working together, rather than testing one function in isolation. For a cache, write a value before reading it: the test then checks observable behavior without depending on how storage is implemented. For a service, run the server and make requests as a client would. section 7.1.3 section 10.5 section 10.5.2

Test a habit-tracker flow

Start the gRPC server with its repository, connect a generated client, then create two habits and list them. Check that both appear. Try creating a habit with a blank name and check for the expected invalid-argument error. Further calls can use the ID returned by creation to tick a habit and verify its status. This exercises the network API and service layers together. section 10.5 section 10.5.2 section 10.7.6

Keep the test repeatable and useful:

  • Let the listener select a free port, start the server while the test makes calls, and stop it when the test ends. section 10.5.2
  • Assert results through the client. Check generated IDs exist, but do not compare unpredictable ID values with fixed strings. section 10.5.2
  • Make list responses deterministic where possible; a defined order makes expected results simpler to check. section 10.5.1
  • Use the in-memory repository while it serves the test. With a real database, decide whether to run a local instance or replace it with a mock. section 10.5.2
Listing 10.26 integration_test.go: Starting the service
func TestIntegration(t *testing.T) {
    grpcServ := newServer(t)
    listener, err := net.Listen("tcp", "")
    require.NoError(t, err)

    wg := sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer wg.Done()
        err = grpcServ.Serve(listener)
        require.NoError(t, err)
    }()
    defer func() {
        // terminate the GRPC server
        grpcServ.Stop()
        // when that is done, and no error were caught, we can end this test
        wg.Wait() #1
    }()

}

func newServer(t *testing.T) *grpc.Server {
    t.Helper() #2
    s := server.New(repository.New(t), t) #3

    grpcServer := grpc.NewServer(
        grpc.UnaryInterceptor(timerInterceptor(s.lgr)))
    api.RegisterHabitsServer(grpcServer, s)
    reflection.Register(grpcServer)

    return grpcServer
}

The server setup shows a test listener, a running gRPC service, and shutdown at the end of the test. section 10.5.2

Run the full suite with `go test -v ./...`. If the integration scenario is too costly for a lightweight run, check `testing.Short()` and skip it under `go test -v -short ./...`. Keep focused unit tests too: a handler test with a prepared URL parameter does not establish that the complete HTTP request flow works. section 10.5.2 section 11.3.1

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through gRPC server setup, client calls, assertions, cleanup, and test commands; leaves real-database integration as a choice.
Examples
Uses cache read/write operations and habit-tracker create, list, tick, and status scenarios.
What it assumes
Assumes familiarity with Go tests and service operations; demonstrates how to start the server and construct its client.
What you can do after
Build it from scratch using Go tests to run a gRPC service and verify client-visible scenarios.
How it connects
Connects unit testing, gRPC, API testing, deterministic ordering, and repository dependencies.

Also explained in

Compare how 31 books explain integration test →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.