Video:

gRPC Error Handling

June 21, 2021

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

Twitter: @Elliot_f

In this video, we are going to be looking at the concept of error handling and defining error responses within our gRPC microservice.

We’ll take a look at how we can define errors and the various codes we can return so that we can help our downstream client applications when an error does occur.

Updating the AddRocket gRPC Handler

Let’s add some validation within the AddRocket gRPC handler method that will validate an incoming UUID within the Rocket object:

if _, err := uuid.Parse(req.Rocket.Id); err != nil {
	errorStatus := status.Error(codes.InvalidArgument, "uuid is not valid")
	log.Print("given uuid is not valid")
	return &rkt.AddRocketResponse{}, errorStatus
}

Acceptance Tests

With this in place, let’s now add a new acceptance test which validates that whenever we hit the endpoint with an invalid UUID it will return the appropriate error message:

// +acceptance

package test

import (
	"context"
	"testing"

	"github.com/TutorialEdge/tutorial-protos/rocket/v1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type RocketTestSuite struct {
	suite.Suite
}

func (s *RocketTestSuite) TestAddRocket() {
	s.T().Run("adds a new rocket successfully", func(t *testing.T) {
		client := GetClient()
		resp, err := client.AddRocket(
			context.Background(),
			&rocket.AddRocketRequest{
				Rocket: &rocket.Rocket{
					Id:   "ae7b0bf0-fe75-4176-b20a-3ca1371f3226",
					Name: "V1",
					Type: "Falcon Heavy",
				},
			},
		)
		assert.NoError(s.T(), err)
		assert.Equal(s.T(), "ae7b0bf0-fe75-4176-b20a-3ca1371f3226", resp.Rocket.Id)
	})

	s.T().Run("validates the uuid in the new rocket is a uuid", func(t *testing.T) {
		client := GetClient()
		_, err := client.AddRocket(
			context.Background(),
			&rocket.AddRocketRequest{
				Rocket: &rocket.Rocket{
					Id:   "not-a-valid-uuid",
					Name: "V1",
					Type: "Falcon Heavy",
				},
			},
		)
		assert.Error(s.T(), err)
		st := status.Convert(err)
		assert.Equal(s.T(), codes.InvalidArgument, st.Code())
	})
}

func TestRocketService(t *testing.T) {
	suite.Run(t, new(RocketTestSuite))
}

Running Our Tests

In one terminal, let’s kick off our gRPC microservice and the postgres container using docker-compose:

$ docker-compose up --build

Next, let’s run our acceptance tests and hit our endpoints with the improved validation:

$ go test ./test -tags=acceptance -v
=== RUN   TestRocketService
=== RUN   TestRocketService/TestAddRocket
=== RUN   TestRocketService/TestAddRocket/adds_a_new_rocket_successfully
=== RUN   TestRocketService/TestAddRocket/validates_the_uuid_in_the_new_rocket_is_a_uuid
--- PASS: TestRocketService (0.02s)
    --- PASS: TestRocketService/TestAddRocket (0.02s)
        --- PASS: TestRocketService/TestAddRocket/adds_a_new_rocket_successfully (0.01s)
        --- PASS: TestRocketService/TestAddRocket/validates_the_uuid_in_the_new_rocket_is_a_uuid (0.01s)
PASS
ok      github.com/TutorialEdge/go-grpc-services-course/test    0.234s

Awesome, we now have the first of our acceptance tests up and running for our gRPC microservice.