Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs

go-testdeep

Build Status Coverage Status Go Report Card GoDoc Version Mentioned in Awesome Go

go-testdeep

Extremely flexible golang deep comparison, extends the go testing package.

Latest news

Synopsis

Make golang tests easy, from simplest usage:

import (
  "testing"

  "github.com/maxatome/go-testdeep/td"
)

func TestMyFunc(t *testing.T) {
  td.Cmp(t, MyFunc(), &Info{Name: "Alice", Age: 42})
}

To a bit more complex one, allowing flexible comparisons using TestDeep operators:

import (
  "testing"

  "github.com/maxatome/go-testdeep/td"
)

func TestMyFunc(t *testing.T) {
  td.Cmp(t, MyFunc(), td.Struct(
    &Info{Name: "Alice"},
    td.StructFields{
      "Age": td.Between(40, 45),
    },
  ))
}

Or anchoring operators directly in literals, as in:

import (
  "testing"

  "github.com/maxatome/go-testdeep/td"
)

func TestMyFunc(tt *testing.T) {
  t := td.NewT(tt)

  t.Cmp(MyFunc(), &Info{
    Name: "Alice",
    Age:  t.Anchor(td.Between(40, 45)).(int),
  })
}

To most complex one, allowing to easily test HTTP API routes, using flexible operators:

import (
  "testing"
  "time"

  "github.com/maxatome/go-testdeep/helpers/tdhttp"
  "github.com/maxatome/go-testdeep/td"
)

type Person struct {
  ID        uint64    `json:"id"`
  Name      string    `json:"name"`
  Age       int       `json:"age"`
  CreatedAt time.Time `json:"created_at"`
}

func TestMyApi(t *testing.T) {
  var id uint64
  var createdAt time.Time

  testAPI := tdhttp.NewTestAPI(t, myAPI) // ← ①

  testAPI.PostJSON("/person", Person{Name: "Bob", Age: 42}). // ← ②
    Name("Create a new Person").
    CmpStatus(http.StatusCreated). // ← ③
    CmpJSONBody(td.JSON(`
// Note that comments are allowed
{
  "id":         $id,          // set by the API/DB
  "name":       "Bob",
  "age":        42,
  "created_at": "$createdAt", // set by the API/DB
}`,
      td.Tag("id", td.Catch(&id, td.NotZero())),        // ← ④
      td.Tag("created_at", td.All(                      // ← ⑤
        td.HasSuffix("Z"),                              // ← ⑥
        td.Smuggle(func(s string) (time.Time, error) {  // ← ⑦
          return time.Parse(time.RFC3339Nano, s)
        }, td.Catch(&createdAt, td.Between(testAPI.SentAt(), time.Now()))), // ← ⑧
      )),
    ))
  if !testAPI.Failed() {
    t.Logf("The new Person ID is %d and was created at %s", id, createdAt)
  }
}
  1. the API handler ready to be tested;
  2. the POST request with automatic JSON marshalling;
  3. the expected response HTTP status should be http.StatusCreated and the line just below, the body should match the JSON operator;
  4. for the $id placeholder, Catch its value: put it in id variable and check it is NotZero;
  5. for the $createdAt placeholder, use the All operator. It combines several operators like a AND;
  6. check that $createdAt date ends with "Z" using HasSuffix. As we expect a RFC3339 date, we require it in UTC time zone;
  7. convert $createdAt date into a time.Time using a custom function thanks to the Smuggle operator;
  8. then Catch the resulting value: put it in createdAt variable and check it is greater or equal than testAPI.SentAt() (the time just before the request is handled) and lesser or equal than time.Now().

See tdhttp helper or the FAQ for details about HTTP API testing.

Example of produced error in case of mismatch:

error output

Description

go-testdeep is a go rewrite and adaptation of wonderful Test::Deep perl.

In golang, comparing data structure is usually done using reflect.DeepEqual or using a package that uses this function behind the scene.

This function works very well, but it is not flexible. Both compared structures must match exactly and when a difference is returned, it is up to the caller to display it. Not easy when comparing big data structures.

The purpose of go-testdeep, via the td package and its helpers, is to do its best to introduce this missing flexibility using "operators", when the expected value (or one of its component) cannot be matched exactly, mixed with some useful comparison functions.

See go-testdeep.zetta.rocks for details.

Installation

$ go get -u github.com/maxatome/go-testdeep

Helpers

The goal of helpers is to make use of go-testdeep even more powerful by providing common features using TestDeep operators behind the scene.

tdhttp or HTTP API testing helper

The package github.com/maxatome/go-testdeep/helpers/tdhttp provides some functions to easily test HTTP handlers.

See tdhttp documentation for details or FAQ for an example of use.

tdutil aka the helper of helpers

The package github.com/maxatome/go-testdeep/helpers/tdutil allows to write unit tests for go-testdeep helpers and so provides some helpful functions.

See tdutil for details.

See also

  • testify: a toolkit with common assertions and mocks that plays nicely with the standard library
  • go-cmp: package for comparing Go values in tests

License

go-testdeep is released under the BSD-style license found in the LICENSE file in the root directory of this source tree.

Internal function deepValueEqual is based on deepValueEqual from reflect golang package licensed under the BSD-style license found in the LICENSE file in the golang repository.

Uses two files (bypass.go & bypasssafe.go) from Go-spew which is licensed under the copyfree ISC License.

Public Domain Gopher provided by Egon Elbre. The Go gopher was designed by Renee French.

FAQ

See FAQ.

Comments
  • Ignore unexported struct fields?

    Ignore unexported struct fields?

    I want to compare all fields in two large structs, except the unexported fields. Is there any way to achieve that without manually specifying each field in a SStruct? I might be missing an operator/helper function, but the best solution that comes to my mind is to compare the JSON representation of the two structs.

    Example for clarification:

    type X struct {
      Field1      int
      Field2      int
      Field3      int
      Field4      int
      Field5      int
      fieldIgnore int // <--- want to ignore this when doing a t.Cmp(x1, x2)
    }
    
  • Comparison of reflect.Value types

    Comparison of reflect.Value types

    Here to annoy you again :P

    I'm aiming to support the encoding of reflect.Type and reflect.Value in my encoding library. go-testdeep has been amazing for tests, but test cases where I compare a reflect.Value values, go-testdeep compares them like a typical struct, and doesn't compare the underlying value inside reflect.Value.

    This means a reflect.Value of a value is only equal if the underlying value is at the same pointer address, and the reflect.Value was created in the same way due to how reflect uses the flag field in reflect.Value.

    In any case, I don't think the semantics of how reflect.Value works is important. For my use case, I'm looking to compare the value inside reflect.Value, and I don't care so much about the reflect.Value itself, e.g if both are addressable or if only one is, although an argument could be made for this. I believe something like

    if got.Type() == reflect.TypeOf(reflect.Value{}) {
        return deepValueEqual(ctx, got.Interface().(reflect.Value), expected.Interface().(reflect.Value))
    }
    

    in deepValueEqual could work, but I'm unsure how this might work with the rest of the codebase. I also know that the ptr field of reflect.Value could introduce an infinite recursion, an extreme case being a reflect.Value that is the value of itself, but less contrived examples do exist.

    I'd be happy to experiment implementing this and submit a PR. Is this something you'd want to support?

  • Recursive SubMap/SuperMap?

    Recursive SubMap/SuperMap?

    Is there a way to compare submaps/supermaps recursively (make sure that partial compare is propagated to nested maps)? Based on what I see, the default behavior is that only the parent map is compared partially, while all nested maps are expected to have exact match.

  • remove Run method from TF interface

    remove Run method from TF interface

    The TestingFT interface includes the Run method, which uses the *testing.T parameter directly, which means that go-testdeep cannot be used with *testing.B or alongside other testing helper packages such as quicktest.

    If you embed testing.TB inside T instead of defining your own testingFT interface, this problem goes away, and additionally you can take advantage of new features added to testing.TB (Cleanup for example) automatically and without breaking your API by adding methods to an interface type.

  • Add support for quotation marks in JSON operators

    Add support for quotation marks in JSON operators

    Problem: We use different operators in JSONs which are then used for comparison. If we add regular operators to json string, e.g. Len, HasPrefix, etc, the JSON becomes invalid and formatters, validators cannot parse the string.

    Proposal: Add support for quotation marks for operators in JSON strings, similarly to currently it is being used for shortcut operators "$^NotEmpty". Ideally it could look similar "$^Len(10)"

  • feat(tdhttp): add CmpCookies

    feat(tdhttp): add CmpCookies

    julien@julien-VirtualBox:~/git/go-testdeep$ go test ./... -cover
    ok      github.com/maxatome/go-testdeep 0.038s  coverage: [no statements]
    ok      github.com/maxatome/go-testdeep/helpers/nocolor 0.047s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/helpers/tdhttp  0.075s  coverage: 99.4% of statements
    ok      github.com/maxatome/go-testdeep/helpers/tdhttp/internal 0.017s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/helpers/tdsuite 0.019s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/helpers/tdutil  0.004s  coverage: 99.4% of statements
    ok      github.com/maxatome/go-testdeep/internal/anchors        0.022s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/color  0.011s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/ctxerr 0.007s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/dark   0.003s  coverage: 95.6% of statements
    ok      github.com/maxatome/go-testdeep/internal/flat   0.011s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/hooks  0.009s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/json   0.006s  coverage: 91.6% of statements
    ?       github.com/maxatome/go-testdeep/internal/location       [no test files]
    ?       github.com/maxatome/go-testdeep/internal/test   [no test files]
    ok      github.com/maxatome/go-testdeep/internal/trace  0.005s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/types  0.008s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/util   0.016s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/internal/visited        0.007s  coverage: 100.0% of statements
    ok      github.com/maxatome/go-testdeep/td      0.209s  coverage: 99.8% of statements
    
  • Difficult to call t.Parallel with td.T

    Difficult to call t.Parallel with td.T

    testdeep encourages wrapping the *testing.T into a *td.T. This unfortunately makes it difficult to call t.Parallel() on the *testing.T once hidden behind the *td.T.

    func TestFoo(tt *testing.T) {
        t := td.NewT(tt)
        
        t.Run("foo", func(t *td.T) {
            // t.Parallel() <<< errors because Parallel() does not exist
            //                  on the testing.TB interface.
            
            t.Cmp(...)
        })
        
        t.Run("bar", func(t *td.T) {
            // t.Parallel()
            
            t.Cmp(...)
        })
    }
    

    It's possible to call t.Parallel() on the *testing.T with some upcasting on the wrapped testing.TB.

    func Parallel(t *td.T) {
    	tp, ok := t.TB.(interface{ Parallel() })
    	if ok {
    		tp.Parallel()
    	}
    }
    

    If you're open to addressing this within testdeep, there are two straightforward options:

    • Add a top-level func Parallel(*td.T) that does the same as above. Usage would then be,

      t.Run("foo", func(t *td.T) {
          td.Parallel(t)
      
          t.Cmp(...)
      })
      
    • Add a Parallel() method on td.T. Usage:

      t.Run("foo", func(t *td.T) {
          t.Parallel()
      
          t.Cmp(...)
      })
      

    The latter is closest to what people already do with *testing.T so it might be preferable.


    Separately, either choice opens room for a setting in testdeep to always call t.Parallel() for subtests invoked with a specific *td.T.Run. For example,

    func TestFoo(tt *testing.T) {
        t := td.NewT(tt).AlwaysParallel(true)
        
        t.Run("foo", func(t *td.T) {
            // already parallel
            t.Cmp(...)
        })
        
        t.Run("bar", func(t *td.T) {
            // already parallel
            t.Cmp(...)
        })
    }
    

    But that can be a separate discussion.

  • deepValueEqual infinite recursion with recursive map types

    deepValueEqual infinite recursion with recursive map types

    I've found an infinite recursion case with go-testdeep. It's a bit contrived I know; I was actually trying to break things. I'm currently developing an encoding library and trying to break it with test cases galore. Interestingly enough encoding/gob, reflect.DeepEqual (fixed in go1.15), and every 3rd party library I've found suffers from this same issue.

    If given a map containing a struct that contains the same map, deepValueEqual recuses infinitely.

    This test case reproduces the issue

    func TestEqualRecursMap(t *testing.T) {
    	gen := func() interface{} {
    		type S struct {
    			Map map[int]S
    		}
    
    		m := make(map[int]S)
    		m[1] = S{
    			Map: m,
    		}
    
    		return m
    	}
    
    	checkOK(t, gen(), gen())
    }
    

    Thanks so much for your work. Let me know if I can help.

  • feat(Code): Code can now officially delegate its comparison

    feat(Code): Code can now officially delegate its comparison

    Using two new kinds of function:

    • func(t *td.T, arg)
    • func(assert, require *td.T, arg)

    this way the usage of *td.T methods is secure. Note that these functions do not return anything.

    At the same time, using a *td.T instance as Cmp* first parameter allows to inherit its configuration.

    td.Cmp(td.Require(t), got, 42)
    

    is the same as:

    td.Require(t).Cmp(got, 42)
    
  • fix(tdsuite): detect wrongly defined hooks

    fix(tdsuite): detect wrongly defined hooks

    Given the 5 hooks:

    • Setup
    • PreTest
    • PostTest
    • BetweenTests
    • Destroy raise an error when a method is defined on a tdsuite with one of these names but without matching the proper interface.
  • anchors not working with table driven tests / multiple t.Cmp calls

    anchors not working with table driven tests / multiple t.Cmp calls

    I'm building some Stripe tests with large, deeply nested structs, where using anchors is very helpful. They work great when I have a single test, but if I'm calling t.Cmp multiple times or have table driven tests, anchors are being compared against the wrong values. Since the anchors have to be set outside the inner test run loop, I'm not sure how td knows when to iterate between them. Am I doing something incorrectly?

    Table driven test

    func Test_payInvoiceWithStripe(stdT *testing.T) {
    	tdt := td.NewT(stdT)
    
    	tests := []struct {
    		name                  string
    		stripeCustomerID      string
    		stripePaymentMethodID string
    		wantPaymentIntent     *stripe.PaymentIntent
    		wantErr               error
    	}{
    		// lots of tdt.A
    	}
    
    	stripeClient := client.New(apiKey, nil)
    	invoice := &billingpb.Invoice{
    		InvoiceId:            111,
    		AccountId:            22222,
    	}
    
    	for i, tt := range tests {
    		tdt.Run(tt.name, func(t *td.T) {
    			gotPaymentIntent, err := chargeCustomer(context.Background(), stripeClient, tt.stripeCustomerID, tt.stripePaymentMethodID, invoice, int64(i))
    			if tt.wantErr == nil {
    				t.CmpNoError(err)
    			} else {
    				t.Cmp(e.Cause(err).(*stripe.Error), e.Cause(tt.wantErr).(*stripe.Error))
    			}
    			t.Cmp(gotPaymentIntent, tt.wantPaymentIntent)
    		})
    	}
    

    Output

            DATA.ChargeID: values differ
            	     got: "ch_3KH0DO4HQ2O7mN7V1o3cxNfi"
            	expected: "<testdeep@anchor#12>"
    

    Thanks for a great library!

  • Add comparison to github.com/go-test/deep

    Add comparison to github.com/go-test/deep

    It would be helpful to have a comparison table or something in the readme to help people understand the difference between this package and https://github.com/go-test/deep. That has more stars and shows up better in search, while this library seems much more active.

  • Expose some internal types, so custom operators can be built

    Expose some internal types, so custom operators can be built

    The request in a nutshell: can enough internal details be exposed to allow building custom operators externally? Ideally in a stable way.

    I very much realize this is not a simple request, but someone has to make it :) Maybe it can serve as a placeholder for planning / discussion. Some context / broader reasoning is below.


    TestDeep looks super interesting! From a quick glance it looks like there are a lot of good, flexible, well-thought-out features, especially around good error messages (that's so rare, thank you!).

    While there are quite a few built-in operators, there are always some edge cases that benefit from custom ones. The JSON operators serve as a nice example of this: it's just a string, but there's a lot of structure that can imply much-better error reporting and a simpler API, so it has a custom operator to improve things.
    Unfortunately, though td/types.go exposes the TestDeep interface, the base type is not exposed, and location.GetLocationer deals with the (afaict) internal-only Location type, so custom operators cannot be built outside this library. There may be other causes as well, this one was just one I saw while reading.

    This is... probably a very reasonable choice for API stability, but somewhat crippling for less-common needs. I assume you don't want to collect every ill-advised and poorly-implemented operator that every user/company/etc dreams up, so being able to build things from the outside would be great. It could also enable "enriching" libraries that enhance TestDeep.

    Is there a way this can be done?

Testing framework for Go. Allows writing self-documenting tests/specifications, and executes them concurrently and safely isolated. [UNMAINTAINED]

GoSpec GoSpec is a BDD-style testing framework for the Go programming language. It allows writing self-documenting tests/specs, and executes them in p

Nov 28, 2022
A next-generation testing tool. Orion provides a powerful DSL to write and automate your acceptance tests

Orion is born to change the way we implement our acceptance tests. It takes advantage of HCL from Hashicorp t o provide a simple DSL to write the acceptance tests.

Aug 31, 2022
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.

GoConvey is awesome Go testing Welcome to GoConvey, a yummy Go testing tool for gophers. Works with go test. Use it in the terminal or browser accordi

Dec 30, 2022
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.

GoConvey is awesome Go testing Welcome to GoConvey, a yummy Go testing tool for gophers. Works with go test. Use it in the terminal or browser accordi

Dec 30, 2022
Record and replay your HTTP interactions for fast, deterministic and accurate tests

go-vcr go-vcr simplifies testing by recording your HTTP interactions and replaying them in future runs in order to provide fast, deterministic and acc

Dec 25, 2022
HTTP mock for Golang: record and replay HTTP/HTTPS interactions for offline testing

govcr A Word Of Warning I'm in the process of partly rewriting govcr to offer better support for cassette mutations. This is necessary because when I

Dec 28, 2022
A simple and expressive HTTP server mocking library for end-to-end tests in Go.

mockhttp A simple and expressive HTTP server mocking library for end-to-end tests in Go. Installation go get -d github.com/americanas-go/mockhttp Exa

Dec 19, 2021
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

Dec 12, 2022
Package for comparing Go values in tests

Package for equality of Go values This package is intended to be a more powerful and safer alternative to reflect.DeepEqual for comparing whether two

Dec 29, 2022
Package has tool to generate workload for vegeta based kube-api stress tests.

Package has tool to generate workload for vegeta based kube-api stress tests.

Nov 22, 2021
A yaml data-driven testing format together with golang testing library

Specimen Yaml-based data-driven testing Specimen is a yaml data format for data-driven testing. This enforces separation between feature being tested

Nov 24, 2022
📡 mock is a simple, cross-platform, cli app to simulate HTTP-based APIs.
 📡 mock is a simple, cross-platform, cli app to simulate HTTP-based APIs.

mock ?? mock is a simple, cross-platform, cli app to simulate HTTP-based APIs. About mock Mock allows you to spin up a local http server based of a .m

May 6, 2022
Golang application focused on tests
Golang application focused on tests

Maceio Golang application that listens for webhook events coming from Github, runs tests previously defined in a configuration file and returns the ou

Sep 8, 2021
S3 etag tests for golang

S3-etag-tests Quickstart Run docker-compose up. Execute tests in /test with $ go

Dec 16, 2021
Behaviour Driven Development tests generator for Golang
Behaviour Driven Development tests generator for Golang

gherkingen It's a Behaviour Driven Development (BDD) tests generator for Golang. It accept a *.feature Cucumber/Gherkin file and generates a test boil

Dec 16, 2022
Robust framework for running complex workload scenarios in isolation, using Go; for integration, e2e tests, benchmarks and more! 💪

e2e Go Module providing robust framework for running complex workload scenarios in isolation, using Go and Docker. For integration, e2e tests, benchma

Jan 5, 2023
How we can run unit tests in parallel mode with failpoint injection taking effect and without injection race

This is a simple demo to show how we can run unit tests in parallel mode with failpoint injection taking effect and without injection race. The basic

Oct 31, 2021
Snapshot - snapshot provides a set of utility functions for creating and loading snapshot files for using snapshot tests.

Snapshot - snapshot provides a set of utility functions for creating and loading snapshot files for using snapshot tests.

Jan 27, 2022
Golang HTTP client testing framework

flute Golang HTTP client testing framework Presentation https://speakerdeck.com/szksh/flute-golang-http-client-testing-framework Overview flute is the

Sep 27, 2022