25% off

Use code FUNCMAIN at checkout for 25% off all premium courses.

Get started →

gRPC Error Handling

June 21, 2021
Elliot Forbes

Elliot Forbes

Course Instructor

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.

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.