Skip to content

Unit Testing Microservices

grittaweisheit edited this page Mar 11, 2020 · 9 revisions

This Page documents our findings about unit testing our microservices

Unit testing seems to be easy for microservices at first glance. The main problem in unit testing microservices is, that the tests should run independently. So whenever we use any protocol or connection to an outside entity we need to mock this.

In some microservices, most (or nearly all) of the functionality is to provide a connection between components. The authentication microservice e.g. takes POST and GET requests, makes a database query and an appropriate response. So the question is if a unit test with mocking the request and the database just to test the correct forwarding of the request is necessary or even useful.

The same result could be reached with an integration test and would eliminate the need for many mockups overhead. In the research we found, that unit testing microservice is a controversial topic.

Unit testing your service’s implementation details isn’t very important; you can achieve more effective coverage by focusing on component testing the http api.

(https://blog.gopheracademy.com/advent-2014/testing-microservices-in-go/)

A microservice may be smaller by definition, but with unit testing, you can go even more granular. A unit test focuses on the smallest part of a testable software to ascertain whether that component works as it should. Renowned software engineer, author, and international speaker Martin Fowler breaks unit testing down into two categories:

Sociable unit testing: This unit testing method tests the behavior of modules by observing changes in their state.

Solitary unit testing: This method focuses on the interactions and collaborations between an object and its dependencies, which are replaced by test doubles.

(https://www.freecodecamp.org/news/these-are-the-most-effective-microservice-testing-strategies-according-to-the-experts-6fb584f2edde/)

Example

You can see example tests for the user management microservice, currently located in osiris.

There, a mock connection is established, so the service server feels like in an actual environment.

func SetUpServerConnection() {
	lis = bufconn.Listen(bufSize)
	s := grpc.NewServer()
	pb.RegisterUserManagementServer(s, &server{db: ldb})
	go func() {
		if err := s.Serve(lis); err != nil {
			fmt.Printf("Server exited with error: %v", err)
		}
	}()
}

For sending requests/grpc calls we need to create a client:

ctx := context.Background()
conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithDialer(bufDialer), grpc.WithInsecure())
if err != nil {
	t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := pb.NewUserManagementClient(conn)

Now we can assemble a request and send it to the service (e.g. a request to get all user data by sending the user's id):

request := &pb.UserId{ Value: 1,}
response, err := client.GetUser(context.Background(), request)