Skip to content

Commit

Permalink
feat: improve consumer error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Jun 21, 2021
1 parent cdaa611 commit ec79f2a
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 17 deletions.
2 changes: 1 addition & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The following package exposes aliases for the most commonly used interfaces for
#### Primary Interface

- `dsl.Pact` was the primary interface. This is now replaced with `NewPactV2` and the `NewPactV3` methods, which will return you a builder for the corresponding specification.
- `Verify` is now `ExecuteTest` to avoid ambiguity with provider side verification.
- `Verify` is now `ExecuteTest` to avoid ambiguity with provider side verification. It also accepts a `*testing.T` argument, to improve error reporting and resolution.

These are available in consumer package: `"github.com/pact-foundation/pact-go/v2/consumer"`

Expand Down
113 changes: 103 additions & 10 deletions consumer/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import (
"log"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"unicode"
"unicode/utf8"

native "github.com/pact-foundation/pact-go/v2/internal/native/mockserver"
logging "github.com/pact-foundation/pact-go/v2/log"
Expand Down Expand Up @@ -125,7 +130,7 @@ func (p *httpMockProvider) configure() error {
// ExecuteTest runs the current test case against a Mock Service.
// Will cleanup interactions between tests within a suite
// and write the pact file if successful
func (p *httpMockProvider) ExecuteTest(integrationTest func(MockServerConfig) error) error {
func (p *httpMockProvider) ExecuteTest(t *testing.T, integrationTest func(MockServerConfig) error) error {
log.Println("[DEBUG] pact verify")

var err error
Expand Down Expand Up @@ -153,7 +158,7 @@ func (p *httpMockProvider) ExecuteTest(integrationTest func(MockServerConfig) er
})

res, mismatches := p.mockserver.Verify(p.config.Port, p.config.PactDir)
p.displayMismatches(mismatches)
p.displayMismatches(t, mismatches)

if err != nil {
return err
Expand All @@ -177,33 +182,121 @@ func (p *httpMockProvider) reset() {
p.configure()
}

// TODO: pretty print this to make it really easy to understand the problems
// TODO: improve / pretty print this to make it really easy to understand the problems
// See existing Pact/Ruby code examples
func (p *httpMockProvider) displayMismatches(mismatches []native.MismatchedRequest) {
func (p *httpMockProvider) displayMismatches(t *testing.T, mismatches []native.MismatchedRequest) {
if len(mismatches) > 0 {

if len(callerInfo()) > 0 {
fmt.Printf("\n\n%s:\n", callerInfo()[len(callerInfo())-1])
}
fmt.Println("\tPact Verification Failed for:", t.Name())
fmt.Println()
fmt.Println("\t\tDiff:")
log.Println("[INFO] pact validation failed, errors: ")
for _, m := range mismatches {
formattedRequest := fmt.Sprintf("%s %s", m.Request.Method, m.Request.Path)
switch m.Type {
case "missing-request":
fmt.Printf("Expected request to: %s, but did not receive one\n", formattedRequest)
fmt.Printf("\t\texpected: \t%s (Expected request that was not received)\n", formattedRequest)
case "request-not-found":
fmt.Printf("Unexpected request was received: %s\n", formattedRequest)
fmt.Printf("\t\tactual: \t%s (Unexpected request was received)\n", formattedRequest)
default:
// TODO:
}

for _, detail := range m.Mismatches {
switch detail.Type {
case "HeaderMismatch":
fmt.Printf("Comparing Header: '%s'\n", detail.Key)
fmt.Println(detail.Mismatch)
fmt.Println("Expected:", detail.Expected)
fmt.Println("Actual:", detail.Actual)
fmt.Printf("\t\t\tComparing Header: '%s'\n", detail.Key)
fmt.Println("\t\t\t", detail.Mismatch)
fmt.Println("\t\t\texpected: \t", detail.Expected)
fmt.Println("\t\t\tactual: \t", detail.Actual)
case "BodyMismatch":
fmt.Printf("\t\t\t%s\n", detail.Mismatch)
fmt.Println("\t\t\texpected:\t", detail.Expected)
fmt.Println("\t\t\tactual:\t\t", detail.Actual)
}
}
}
fmt.Println()
fmt.Println()
}
}

// Stolen from "github.com/stretchr/testify/assert"
func callerInfo() []string {

var pc uintptr
var ok bool
var file string
var line int
var name string

callers := []string{}
for i := 0; ; i++ {
pc, file, line, ok = runtime.Caller(i)
if !ok {
// The breaks below failed to terminate the loop, and we ran off the
// end of the call stack.
break
}

// This is a huge edge case, but it will panic if this is the case, see #180
if file == "<autogenerated>" {
break
}

f := runtime.FuncForPC(pc)
if f == nil {
break
}
name = f.Name()

// testing.tRunner is the standard library function that calls
// tests. Subtests are called directly by tRunner, without going through
// the Test/Benchmark/Example function that contains the t.Run calls, so
// with subtests we should break when we hit tRunner, without adding it
// to the list of callers.
if name == "testing.tRunner" {
break
}

parts := strings.Split(file, "/")
file = parts[len(parts)-1]
if len(parts) > 1 {
dir := parts[len(parts)-2]
if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" {
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
}
}

// Drop the package
segments := strings.Split(name, ".")
name = segments[len(segments)-1]
if isTest(name, "Test") ||
isTest(name, "Benchmark") ||
isTest(name, "Example") {
break
}
}

return callers
}

// Stolen from the `go test` tool.
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
r, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(r)
}

// writePact may be called after each interaction with a mock server is completed
Expand Down
2 changes: 1 addition & 1 deletion docs/consumer.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestProductAPIClient(t *testing.T) {
WithBodyMatch(&Product{}) // This uses struct tags for matchers

// Act: test our API client behaves correctly
err = mockProvider.ExecuteTest(func(config MockServerConfig) error {
err = mockProvider.ExecuteTest(t, func(config MockServerConfig) error {
// Initialise the API client and point it at the Pact mock server
// Pact spins up a dedicated mock server for each test
client := newClient(config.Host, config.Port)
Expand Down
2 changes: 1 addition & 1 deletion examples/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestProductAPIClient(t *testing.T) {
WithBodyMatch(&Product{})

// Act: test our API client behaves correctly
err = mockProvider.ExecuteTest(func(config MockServerConfig) error {
err = mockProvider.ExecuteTest(t, func(config MockServerConfig) error {
// Initialise the API client and point it at the Pact mock server
client := newClient(config.Host, config.Port)

Expand Down
8 changes: 4 additions & 4 deletions examples/consumer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestConsumerV2(t *testing.T) {
})

// Execute pact test
err = mockProvider.ExecuteTest(test)
err = mockProvider.ExecuteTest(t, test)
assert.NoError(t, err)
}

Expand Down Expand Up @@ -87,7 +87,7 @@ func TestConsumerV2_Match(t *testing.T) {
WithBodyMatch(&User{})

// Execute pact test
err = mockProvider.ExecuteTest(test)
err = mockProvider.ExecuteTest(t, test)
assert.NoError(t, err)
}

Expand Down Expand Up @@ -144,7 +144,7 @@ func TestConsumerV3(t *testing.T) {
})

// Execute pact test
err = mockProvider.ExecuteTest(test)
err = mockProvider.ExecuteTest(t, test)
assert.NoError(t, err)
}

Expand Down Expand Up @@ -193,7 +193,7 @@ func TestConsumerV2AllInOne(t *testing.T) {
})

// Execute pact test
err = mockProvider.ExecuteTest(legacyTest)
err = mockProvider.ExecuteTest(t, legacyTest)
assert.NoError(t, err)
}

Expand Down

0 comments on commit ec79f2a

Please sign in to comment.