liveBook · Manning

Unit test

As explained in gRPC Microservices in Go

A unit test verifies the behavior of an isolated unit of software, often across defined execution scenarios.

A unit test checks one software component—the system under test (SUT)—in isolation. In a microservice, that can mean testing order placement without running the database or Payment service. Automated tests give faster feedback than manually running the full service flow. section 7.1 section 7.0

Test scenarios, not merely whether a function runs:

  • Replace the database and payment dependencies with mocks that implement their interfaces. Configure Save and Charge to return the result needed for each scenario. section 7.2.4
  • For the happy path, have both calls succeed and assert that PlaceOrder returns no error. section 7.2.4
  • For persistence failure, make Save return an error and assert that PlaceOrder returns its message. For payment failure, assert the resulting gRPC status message and validation-error detail. section 7.2.4
func Test_Should_Return_Error_When_Payment_Fail(t *testing.T) {
    payment := new(mockedPayment)
    db := new(mockedDb)
    payment.On("Charge", mock.Anything).Return(errors.New("insufficient 
     balance"))                                                  #1
    db.On("Save", mock.Anything).Return(nil)
 
    application := NewApplication(db, payment)
    _, err := application.PlaceOrder(domain.Order{
        CustomerID: 123,
        OrderItems: []domain.OrderItem{
            {
                ProductCode: "bag",
                UnitPrice:   2.5,
                Quantity:    6,
            },
        },
        CreatedAt: 0,
    })
    st, _ := status.FromError(err)                                 #2
    assert.Equal(t, st.Message(), "order creation failed")         #3
    assert.Equal(t, 
     st.Details()[0].(*errdetails.BadRequest).FieldViolations[0]
     .Description, "insufficient balance")                       #4
    assert.Equal(t, st.Code(), codes.InvalidArgument)              #5
}

The payment-failure test shows why checking only for a non-nil error is insufficient: the assertion also inspects the status and field-violation detail returned to the caller. section 7.2.4

Run the Go tests with `go test ./... -cover -coverprofile=coverage.out` to generate a coverage report. Keep unit tests numerous because their isolation makes them fast and inexpensive, but use integration tests for interactions between modules and end-to-end tests for complete flows. Test results and coverage can also help verify pull requests in CI. section 7.2.4 section 7.1 section 7.6

How gRPC Microservices in Go covers it

How deep it goes
Works through interface mocks and success and failure assertions; leaves real database interaction to integration tests.
Examples
Uses Go PlaceOrder tests for successful placement, database persistence failure, and payment validation errors.
What it assumes
Assumes an existing Go service with database and payment interfaces; shows how to mock them for tests.
What you can do after
Build it from scratch for an Order service operation using mocked dependencies, then run tests and generate coverage.
How it connects
Links the system under test and mocking to the testing pyramid, integration tests, end-to-end tests, and test coverage.

Also explained in

Compare how 9 books explain unit test →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of gRPC Microservices in Go. The section links point to the parts of the book it draws on.