Back to godoc.org

Package td

v1.7.0
Latest Go to latest
Published: Jul 19, 2020 | License: BSD-2-Clause | Module: github.com/maxatome/go-testdeep

Overview

Package td (aka go-testdeep) allows extremely flexible deep comparison, it is built for testing.

It is a go rewrite and adaptation of wonderful Test::Deep perl module (see https://metacpan.org/pod/Test::Deep).

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.

The purpose of td package 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.

See https://go-testdeep.zetta.rocks/ for details, and for easy HTTP API testing, see the tdhttp helper https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdhttp

Example of use

Imagine a function returning a struct containing a newly created database record. The Id and the CreatedAt fields are set by the database layer:

type Record struct {
  Id        uint64
  Name      string
  Age       int
  CreatedAt time.Time
}

func CreateRecord(name string, age int) (*Record, error) {
  // Do INSERT INTO … and return newly created record or error if it failed
}

Using standard testing package

To check the freshly created record contents using standard testing package, we have to do something like that:

import (
  "testing"
  "time"
)

func TestCreateRecord(t *testing.T) {
  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  if err != nil {
    t.Errorf("An error occurred: %s", err)
  } else {
    expected := Record{Name: "Bob", Age: 23}

    if record.Id == 0 {
      t.Error("Id probably not initialized")
    }
    if before.After(record.CreatedAt) ||
      time.Now().Before(record.CreatedAt) {
      t.Errorf("CreatedAt field not expected: %s", record.CreatedAt)
    }
    if record.Name != expected.Name {
      t.Errorf("Name field differs, got=%s, expected=%s",
        record.Name, expected.Name)
    }
    if record.Age != expected.Age {
      t.Errorf("Age field differs, got=%s, expected=%s",
        record.Age, expected.Age)
    }
  }
}

Using basic go-testdeep approach

td package, via its Cmp* functions, handles the tests and all the error message boiler plate. Let's do it:

import (
  "testing"
  "time"

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

func TestCreateRecord(t *testing.T) {
  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  if td.CmpNoError(t, err) {
    td.Cmp(t, record.Id, td.NotZero(), "Id initialized")
    td.Cmp(t, record.Name, "Bob")
    td.Cmp(t, record.Age, 23)
    td.Cmp(t, record.CreatedAt, td.Between(before, time.Now()))
  }
}

As we cannot guess the Id field value before its creation, we use the NotZero operator to check it is set by CreateRecord() call. The same it true for the creation date field CreatedAt. Thanks to the Between operator we can check it is set with a value included between the date before CreateRecord() call and the date just after.

Note that if `Id` and `CreateAt` could be known in advance, we could simply do:

import (
  "testing"
  "time"

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

func TestCreateRecord(t *testing.T) {
  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  if td.CmpNoError(t, err) {
    td.Cmp(t, record, &Record{
      Id:        1234,
      Name:      "Bob",
      Age:       23,
      CreatedAt: time.Date(2019, time.May, 1, 12, 13, 14, 0, time.UTC),
   })
  }
}

But unfortunately, it is common to not know exactly the value of some fields…

Using advanced go-testdeep technique

Of course we can test struct fields one by one, but with go-testdeep, the whole struct can be compared with one Cmp call.

import (
  "testing"
  "time"

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

func TestCreateRecord(t *testing.T) {
  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  if td.CmpNoError(t, err) {
    td.Cmp(t, record,
      td.Struct(
        &Record{
          Name: "Bob",
          Age:  23,
        },
        td.StructFields{
          "Id":        td.NotZero(),
          "CreatedAt": td.Between(before, time.Now()),
        }),
      "Newly created record")
  }
}

See the use of the Struct operator. It is needed here to overcome the go static typing system and so use other go-testdeep operators for some fields, here NotZero and Between.

Not only structs can be compared. A lot of operators can be found below to cover most (all?) needed tests. See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#TestDeep

Using go-testdeep Cmp shortcuts

The Cmp function is the keystone of this package, but to make the writing of tests even easier, the family of Cmp* functions are provided and act as shortcuts. Using CmpStruct function, the previous example can be written as:

import (
  "testing"
  "time"

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

func TestCreateRecord(t *testing.T) {
  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  if td.CmpNoError(t, err) {
    td.CmpStruct(t, record,
      &Record{
        Name: "Bob",
        Age:  23,
      },
      td.StructFields{
        "Id":        td.NotZero(),
        "CreatedAt": td.Between(before, time.Now()),
      },
      "Newly created record")
  }
}

Using T type

testing.T can be encapsulated in td.T type, simplifying again the test:

import (
  "testing"
  "time"

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

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

  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  if t.CmpNoError(err) {
    t.RootName("RECORD").Struct(record,
      &Record{
        Name: "Bob",
        Age:  23,
      },
      td.StructFields{
        "Id":        td.NotZero(),
        "CreatedAt": td.Between(before, time.Now()),
      },
      "Newly created record")
  }
}

Note the use of RootName method, it allows to name what we are going to test, instead of the default "DATA".

A step further with operator anchoring

Overcome the go static typing system using the Struct operator is sometimes heavy. Especially when structs are nested, as the Struct operator needs to be used for each level surrounding the level in which an operator is involved. Operator anchoring feature has been designed to avoid this heaviness:

import (
  "testing"
  "time"

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

func TestCreateRecord(tt *testing.T) {
  before := time.Now().Truncate(time.Second)
  record, err := CreateRecord()

  t := td.NewT(tt) // operator anchoring needs a *td.T instance

  if t.CmpNoError(err) {
    t.Cmp(record,
      &Record{
        Name:      "Bob",
        Age:       23,
        ID:        t.A(td.NotZero(), uint64(0)).(uint64),
        CreatedAt: t.A(td.Between(before, time.Now())).(time.Time),
      },
      "Newly created record")
  }
}

See the A method (or its full name alias Anchor) documentation for details.

Example

Code:

t := &testing.T{}

dateToTime := func(str string) time.Time {
	t, err := time.Parse(time.RFC3339, str)
	if err != nil {
		panic(err)
	}
	return t
}

type PetFamily uint8

const (
	Canidae PetFamily = 1
	Felidae PetFamily = 2
)

type Pet struct {
	Name     string
	Birthday time.Time
	Family   PetFamily
}

type Master struct {
	Name         string
	AnnualIncome int
	Pets         []*Pet
}

masters := []Master{
	{
		Name:         "Bob Smith",
		AnnualIncome: 25000,
		Pets: []*Pet{
			{
				Name:     "Quizz",
				Birthday: dateToTime("2010-11-05T10:00:00Z"),
				Family:   Canidae,
			},
			{
				Name:     "Charlie",
				Birthday: dateToTime("2013-05-11T08:00:00Z"),
				Family:   Canidae,
			},
		},
	},
	{
		Name:         "John Doe",
		AnnualIncome: 38000,
		Pets: []*Pet{
			{
				Name:     "Coco",
				Birthday: dateToTime("2015-08-05T18:00:00Z"),
				Family:   Felidae,
			},
			{
				Name:     "Lucky",
				Birthday: dateToTime("2014-04-17T07:00:00Z"),
				Family:   Canidae,
			},
		},
	},
}

ok := td.Cmp(t, masters, td.All(
	td.Len(td.Gt(0)),
	td.ArrayEach(

		td.Struct(Master{}, td.StructFields{

			"Name": td.Re(`^[A-Z][a-z]+ [A-Z][a-z]+\z`),

			"AnnualIncome": td.Gt(10000),
			"Pets": td.ArrayEach(

				td.Struct(&Pet{}, td.StructFields{

					"Name": td.Re(`^[A-Z][a-z]+\z`),
					"Birthday": td.All(

						td.Between(dateToTime("2010-01-01T00:00:00Z"), time.Now()),

						td.Code(func(t time.Time) bool {
							return t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0
						}),
					),

					"Family": td.Any(Canidae, Felidae),
				}),
			),
		}),
	),
))
fmt.Println(ok)
true

Index

Examples

Package Files

Variables

var DefaultContextConfig = ContextConfig{
	RootName:       contextDefaultRootName,
	MaxErrors:      getMaxErrorsFromEnv(),
	FailureIsFatal: false,
	UseEqual:       false,
	BeLax:          false,
}

DefaultContextConfig is the default configuration used to render tests failures. If overridden, new settings will impact all Cmp* functions and *T methods (if not specifically configured.)

func AddAnchorableStructType

func AddAnchorableStructType(fn interface{})

AddAnchorableStructType declares a struct type as anchorable. "fn" is a function allowing to return a unique and identifiable instance of the struct type.

"fn" has to have the following signature:

func (nextAnchor int) TYPE

TYPE is the struct type to make anchorable and "nextAnchor" is an index to allow to differentiate several instances of the same type.

For example, the time.Time type which is anchrorable by default, could be declared as:

AddAnchorableStructType(func (nextAnchor int) time.Time {
  return time.Unix(int64(math.MaxInt64-1000424443-nextAnchor), 42)
})

Just as a note, the 1000424443 constant allows to avoid to flirt with the math.MaxInt64 extreme limit and so avoid possible collision with real world values.

It panics if the provided "fn" is not a function or if it has not the expected signature (see above).

func AssertRequire

func AssertRequire(t testing.TB, config ...ContextConfig) (*T, *T)

AssertRequire returns 2 instances of T. The first one called "assert" with FailureIsFatal flag set to false, and the second called "require" with FailureIsFatal flag set to true.

assert, require := AssertRequire(t)

is roughly equivalent to:

assert, require := Assert(t), Require(t)

See NewT documentation for usefulness of "config" optional parameter.

func Cmp

func Cmp(t TestingT, got, expected interface{}, args ...interface{}) bool

Cmp returns true if "got" matches "expected". "expected" can be the same type as "got" is, or contains some TestDeep operators. If "got" does not match "expected", it returns false and the reason of failure is logged with the help of "t" Error() method.

got := "foobar"
td.Cmp(t, got, "foobar")            // succeeds
td.Cmp(t, got, td.HasPrefix("foo")) // succeeds

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

func CmpAll

func CmpAll(t TestingT, got interface{}, expectedValues []interface{}, args ...interface{}) bool

CmpAll is a shortcut for:

td.Cmp(t, got, td.All(expectedValues...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#All for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "foo/bar"

ok := td.CmpAll(t, got, []interface{}{td.Re("o/b"), td.HasSuffix("bar"), "foo/bar"},
	"checks value %s", got)
fmt.Println(ok)

ok = td.CmpAll(t, got, []interface{}{td.Re("o/b"), td.HasSuffix("bar"), "fooX/Ybar"},
	"checks value %s", got)
fmt.Println(ok)

regOps := td.Flatten([]td.TestDeep{td.Re("o/b"), td.Re(`^fo`), td.Re(`ar$`)})
ok = td.CmpAll(t, got, []interface{}{td.HasPrefix("foo"), regOps, td.HasSuffix("bar")},
	"checks all operators against value %s", got)
fmt.Println(ok)
true
false
true

func CmpAny

func CmpAny(t TestingT, got interface{}, expectedValues []interface{}, args ...interface{}) bool

CmpAny is a shortcut for:

td.Cmp(t, got, td.Any(expectedValues...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Any for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "foo/bar"

ok := td.CmpAny(t, got, []interface{}{td.Re("zip"), td.HasSuffix("bar")},
	"checks value %s", got)
fmt.Println(ok)

ok = td.CmpAny(t, got, []interface{}{td.Re("zip"), td.HasSuffix("foo")},
	"checks value %s", got)
fmt.Println(ok)

regOps := td.Flatten([]td.TestDeep{td.Re("a/c"), td.Re(`^xx`), td.Re(`ar$`)})
ok = td.CmpAny(t, got, []interface{}{td.HasPrefix("xxx"), regOps, td.HasSuffix("zip")},
	"check at least one operator matches value %s", got)
fmt.Println(ok)
true
false
true

func CmpArray

func CmpArray(t TestingT, got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool

CmpArray is a shortcut for:

td.Cmp(t, got, td.Array(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Array for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Array)

Code:

t := &testing.T{}

got := [3]int{42, 58, 26}

ok := td.CmpArray(t, got, [3]int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks array %v", got)
fmt.Println(ok)
true
Example (TypedArray)

Code:

t := &testing.T{}

type MyArray [3]int

got := MyArray{42, 58, 26}

ok := td.CmpArray(t, got, MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks typed array %v", got)
fmt.Println(ok)

ok = td.CmpArray(t, &got, &MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks pointer on typed array %v", got)
fmt.Println(ok)

ok = td.CmpArray(t, &got, &MyArray{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed array %v", got)
fmt.Println(ok)

ok = td.CmpArray(t, &got, (*MyArray)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed array %v", got)
fmt.Println(ok)
true
true
true
true

func CmpArrayEach

func CmpArrayEach(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool

CmpArrayEach is a shortcut for:

td.Cmp(t, got, td.ArrayEach(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ArrayEach for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Array)

Code:

t := &testing.T{}

got := [3]int{42, 58, 26}

ok := td.CmpArrayEach(t, got, td.Between(25, 60),
	"checks each item of array %v is in [25 .. 60]", got)
fmt.Println(ok)
true
Example (Slice)

Code:

t := &testing.T{}

got := []int{42, 58, 26}

ok := td.CmpArrayEach(t, got, td.Between(25, 60),
	"checks each item of slice %v is in [25 .. 60]", got)
fmt.Println(ok)
true
Example (TypedArray)

Code:

t := &testing.T{}

type MyArray [3]int

got := MyArray{42, 58, 26}

ok := td.CmpArrayEach(t, got, td.Between(25, 60),
	"checks each item of typed array %v is in [25 .. 60]", got)
fmt.Println(ok)

ok = td.CmpArrayEach(t, &got, td.Between(25, 60),
	"checks each item of typed array pointer %v is in [25 .. 60]", got)
fmt.Println(ok)
true
true
Example (TypedSlice)

Code:

t := &testing.T{}

type MySlice []int

got := MySlice{42, 58, 26}

ok := td.CmpArrayEach(t, got, td.Between(25, 60),
	"checks each item of typed slice %v is in [25 .. 60]", got)
fmt.Println(ok)

ok = td.CmpArrayEach(t, &got, td.Between(25, 60),
	"checks each item of typed slice pointer %v is in [25 .. 60]", got)
fmt.Println(ok)
true
true

func CmpBag

func CmpBag(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool

CmpBag is a shortcut for:

td.Cmp(t, got, td.Bag(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Bag for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.CmpBag(t, got, []interface{}{1, 1, 2, 3, 5, 8, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.CmpBag(t, got, []interface{}{1, 2, 3, 5, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

got = []int{1, 3, 5, 8, 2}

ok = td.CmpBag(t, got, []interface{}{1, 1, 2, 3, 5, 8, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.CmpBag(t, got, []interface{}{1, 2, 3, 5, td.Gt(7)},
	"checks all items are present, in any order")
fmt.Println(ok)

expected := []int{1, 2, 3, 5}
ok = td.CmpBag(t, got, []interface{}{td.Flatten(expected), td.Gt(7)},
	"checks all expected items are present, in any order")
fmt.Println(ok)
true
false
false
true
true

func CmpBetween

func CmpBetween(t TestingT, got interface{}, from interface{}, to interface{}, bounds BoundsKind, args ...interface{}) bool

CmpBetween is a shortcut for:

td.Cmp(t, got, td.Between(from, to, bounds), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Between for details.

Between() optional parameter "bounds" is here mandatory. td.BoundsInIn value should be passed to mimic its absence in original Between() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.CmpBetween(t, got, 154, 156, td.BoundsInIn,
	"checks %v is in [154 .. 156]", got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, 154, 156, td.BoundsInIn,
	"checks %v is in [154 .. 156]", got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, 154, 156, td.BoundsInOut,
	"checks %v is in [154 .. 156[", got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, 154, 156, td.BoundsOutIn,
	"checks %v is in ]154 .. 156]", got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, 154, 156, td.BoundsOutOut,
	"checks %v is in ]154 .. 156[", got)
fmt.Println(ok)
true
true
false
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.CmpBetween(t, got, "aaa", "abc", td.BoundsInIn,
	`checks "%v" is in ["aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, "aaa", "abc", td.BoundsInIn,
	`checks "%v" is in ["aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, "aaa", "abc", td.BoundsInOut,
	`checks "%v" is in ["aaa" .. "abc"[`, got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, "aaa", "abc", td.BoundsOutIn,
	`checks "%v" is in ]"aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = td.CmpBetween(t, got, "aaa", "abc", td.BoundsOutOut,
	`checks "%v" is in ]"aaa" .. "abc"[`, got)
fmt.Println(ok)
true
true
false
true
false

func CmpCap

func CmpCap(t TestingT, got interface{}, expectedCap interface{}, args ...interface{}) bool

CmpCap is a shortcut for:

td.Cmp(t, got, td.Cap(expectedCap), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Cap for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := make([]int, 0, 12)

ok := td.CmpCap(t, got, 12, "checks %v capacity is 12", got)
fmt.Println(ok)

ok = td.CmpCap(t, got, 0, "checks %v capacity is 0", got)
fmt.Println(ok)

got = nil

ok = td.CmpCap(t, got, 0, "checks %v capacity is 0", got)
fmt.Println(ok)
true
false
true
Example (Operator)

Code:

t := &testing.T{}

got := make([]int, 0, 12)

ok := td.CmpCap(t, got, td.Between(10, 12),
	"checks %v capacity is in [10 .. 12]", got)
fmt.Println(ok)

ok = td.CmpCap(t, got, td.Gt(10),
	"checks %v capacity is in [10 .. 12]", got)
fmt.Println(ok)
true
true

func CmpCode

func CmpCode(t TestingT, got interface{}, fn interface{}, args ...interface{}) bool

CmpCode is a shortcut for:

td.Cmp(t, got, td.Code(fn), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Code for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "12"

ok := td.CmpCode(t, got, func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 10 && n < 100
},
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)

ok = td.CmpCode(t, got, func(num string) (bool, string) {
	n, err := strconv.Atoi(num)
	if err != nil {
		return false, "not a number"
	}
	if n > 10 && n < 100 {
		return true, ""
	}
	return false, "not in ]10 .. 100["
},
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)

ok = td.CmpCode(t, got, func(num string) error {
	n, err := strconv.Atoi(num)
	if err != nil {
		return err
	}
	if n > 10 && n < 100 {
		return nil
	}
	return fmt.Errorf("%d not in ]10 .. 100[", n)
},
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)
true
true
true

func CmpContains

func CmpContains(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool

CmpContains is a shortcut for:

td.Cmp(t, got, td.Contains(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Contains for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (ArraySlice)

Code:

t := &testing.T{}

ok := td.CmpContains(t, [...]int{11, 22, 33, 44}, 22)
fmt.Println("array contains 22:", ok)

ok = td.CmpContains(t, [...]int{11, 22, 33, 44}, td.Between(20, 25))
fmt.Println("array contains at least one item in [20 .. 25]:", ok)

ok = td.CmpContains(t, []int{11, 22, 33, 44}, 22)
fmt.Println("slice contains 22:", ok)

ok = td.CmpContains(t, []int{11, 22, 33, 44}, td.Between(20, 25))
fmt.Println("slice contains at least one item in [20 .. 25]:", ok)

ok = td.CmpContains(t, []int{11, 22, 33, 44}, []int{22, 33})
fmt.Println("slice contains the sub-slice [22, 33]:", ok)
array contains 22: true
array contains at least one item in [20 .. 25]: true
slice contains 22: true
slice contains at least one item in [20 .. 25]: true
slice contains the sub-slice [22, 33]: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.CmpContains(t, got, "oob", "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = td.CmpContains(t, got, 'b', "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = td.CmpContains(t, got, byte('a'), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = td.CmpContains(t, got, td.Between('n', 'p'), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Map)

Code:

t := &testing.T{}

ok := td.CmpContains(t, map[string]int{"foo": 11, "bar": 22, "zip": 33}, 22)
fmt.Println("map contains value 22:", ok)

ok = td.CmpContains(t, map[string]int{"foo": 11, "bar": 22, "zip": 33}, td.Between(20, 25))
fmt.Println("map contains at least one value in [20 .. 25]:", ok)
map contains value 22: true
map contains at least one value in [20 .. 25]: true
Example (Nil)

Code:

t := &testing.T{}

num := 123
got := [...]*int{&num, nil}

ok := td.CmpContains(t, got, nil)
fmt.Println("array contains untyped nil:", ok)

ok = td.CmpContains(t, got, (*int)(nil))
fmt.Println("array contains *int nil:", ok)

ok = td.CmpContains(t, got, td.Nil())
fmt.Println("array contains Nil():", ok)

ok = td.CmpContains(t, got, (*byte)(nil))
fmt.Println("array contains *byte nil:", ok)
array contains untyped nil: true
array contains *int nil: true
array contains Nil(): true
array contains *byte nil: false
Example (String)

Code:

t := &testing.T{}

got := "foobar"

ok := td.CmpContains(t, got, "oob", "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = td.CmpContains(t, got, []byte("oob"), "checks %s", got)
fmt.Println("contains `oob` []byte:", ok)

ok = td.CmpContains(t, got, 'b', "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = td.CmpContains(t, got, byte('a'), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = td.CmpContains(t, got, td.Between('n', 'p'), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains `oob` []byte: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.CmpContains(t, got, "oob", "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = td.CmpContains(t, got, 'b', "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = td.CmpContains(t, got, byte('a'), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = td.CmpContains(t, got, td.Between('n', 'p'), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true

func CmpContainsKey

func CmpContainsKey(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool

CmpContainsKey is a shortcut for:

td.Cmp(t, got, td.ContainsKey(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ContainsKey for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpContainsKey(t, map[string]int{"foo": 11, "bar": 22, "zip": 33}, "foo")
fmt.Println(`map contains key "foo":`, ok)

ok = td.CmpContainsKey(t, map[int]bool{12: true, 24: false, 42: true, 51: false}, td.Between(40, 50))
fmt.Println("map contains at least a key in [40 .. 50]:", ok)

ok = td.CmpContainsKey(t, map[string]int{"FOO": 11, "bar": 22, "zip": 33}, td.Smuggle(strings.ToLower, "foo"))
fmt.Println(`map contains key "foo" without taking case into account:`, ok)
map contains key "foo": true
map contains at least a key in [40 .. 50]: true
map contains key "foo" without taking case into account: true
Example (Nil)

Code:

t := &testing.T{}

num := 1234
got := map[*int]bool{&num: false, nil: true}

ok := td.CmpContainsKey(t, got, nil)
fmt.Println("map contains untyped nil key:", ok)

ok = td.CmpContainsKey(t, got, (*int)(nil))
fmt.Println("map contains *int nil key:", ok)

ok = td.CmpContainsKey(t, got, td.Nil())
fmt.Println("map contains Nil() key:", ok)

ok = td.CmpContainsKey(t, got, (*byte)(nil))
fmt.Println("map contains *byte nil key:", ok)
map contains untyped nil key: true
map contains *int nil key: true
map contains Nil() key: true
map contains *byte nil key: false

func CmpDeeply

func CmpDeeply(t TestingT, got, expected interface{}, args ...interface{}) bool

CmpDeeply works the same as Cmp and is still available for compatibility purpose. Use shorter Cmp in new code.

got := "foobar"
td.CmpDeeply(t, got, "foobar")            // succeeds
td.CmpDeeply(t, got, td.HasPrefix("foo")) // succeeds

func CmpEmpty

func CmpEmpty(t TestingT, got interface{}, args ...interface{}) bool

CmpEmpty is a shortcut for:

td.Cmp(t, got, td.Empty(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Empty for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpEmpty(t, nil)
fmt.Println(ok)

ok = td.CmpEmpty(t, (*int)(nil))
fmt.Println(ok)

ok = td.CmpEmpty(t, "")
fmt.Println(ok)

ok = td.CmpEmpty(t, 0)
fmt.Println(ok)

ok = td.CmpEmpty(t, (map[string]int)(nil))
fmt.Println(ok)

ok = td.CmpEmpty(t, map[string]int{})
fmt.Println(ok)

ok = td.CmpEmpty(t, ([]int)(nil))
fmt.Println(ok)

ok = td.CmpEmpty(t, []int{})
fmt.Println(ok)

ok = td.CmpEmpty(t, []int{3})
fmt.Println(ok)

ok = td.CmpEmpty(t, [3]int{})
fmt.Println(ok)
true
false
true
false
true
true
true
true
false
false
Example (Pointers)

Code:

t := &testing.T{}

type MySlice []int

ok := td.CmpEmpty(t, MySlice{})
fmt.Println(ok)

ok = td.CmpEmpty(t, &MySlice{})
fmt.Println(ok)

l1 := &MySlice{}
l2 := &l1
l3 := &l2
ok = td.CmpEmpty(t, &l3)
fmt.Println(ok)

// But not for others types as:
type MyStruct struct {
	Value int
}

ok = td.CmpEmpty(t, &MyStruct{})
fmt.Println(ok)
true
true
true
false

func CmpError

func CmpError(t TestingT, got error, args ...interface{}) bool

CmpError checks that "got" is non-nil error.

_, err := MyFunction(1, 2, 3)
td.CmpError(t, err, "MyFunction(1, 2, 3) should return an error")

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := fmt.Errorf("Error #%d", 42)
ok := td.CmpError(t, got, "An error occurred")
fmt.Println(ok)

got = nil
ok = td.CmpError(t, got, "An error occurred")
fmt.Println(ok)
true
false

func CmpFalse

func CmpFalse(t TestingT, got bool, args ...interface{}) bool

CmpFalse is a shortcut for:

td.Cmp(t, got, false, args...)

Returns true if the test is OK, false if it fails.

td.CmpFalse(t, IsAvailable(x), "x should not be available")

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := false
ok := td.CmpFalse(t, got, "check that got is false!")
fmt.Println(ok)

got = true
ok = td.CmpFalse(t, got, "check that got is false!")
fmt.Println(ok)
true
false

func CmpGt

func CmpGt(t TestingT, got interface{}, minExpectedValue interface{}, args ...interface{}) bool

CmpGt is a shortcut for:

td.Cmp(t, got, td.Gt(minExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Gt for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.CmpGt(t, got, 155, "checks %v is > 155", got)
fmt.Println(ok)

ok = td.CmpGt(t, got, 156, "checks %v is > 156", got)
fmt.Println(ok)
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.CmpGt(t, got, "abb", `checks "%v" is > "abb"`, got)
fmt.Println(ok)

ok = td.CmpGt(t, got, "abc", `checks "%v" is > "abc"`, got)
fmt.Println(ok)
true
false

func CmpGte

func CmpGte(t TestingT, got interface{}, minExpectedValue interface{}, args ...interface{}) bool

CmpGte is a shortcut for:

td.Cmp(t, got, td.Gte(minExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Gte for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.CmpGte(t, got, 156, "checks %v is ≥ 156", got)
fmt.Println(ok)

ok = td.CmpGte(t, got, 155, "checks %v is ≥ 155", got)
fmt.Println(ok)

ok = td.CmpGte(t, got, 157, "checks %v is ≥ 157", got)
fmt.Println(ok)
true
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.CmpGte(t, got, "abc", `checks "%v" is ≥ "abc"`, got)
fmt.Println(ok)

ok = td.CmpGte(t, got, "abb", `checks "%v" is ≥ "abb"`, got)
fmt.Println(ok)

ok = td.CmpGte(t, got, "abd", `checks "%v" is ≥ "abd"`, got)
fmt.Println(ok)
true
true
false

func CmpHasPrefix

func CmpHasPrefix(t TestingT, got interface{}, expected string, args ...interface{}) bool

CmpHasPrefix is a shortcut for:

td.Cmp(t, got, td.HasPrefix(expected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#HasPrefix for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "foobar"

ok := td.CmpHasPrefix(t, got, "foo", "checks %s", got)
fmt.Println("using string:", ok)

ok = td.Cmp(t, []byte(got), td.HasPrefix("foo"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.CmpHasPrefix(t, got, "foo", "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.CmpHasPrefix(t, got, "foo", "checks %s", got)
fmt.Println(ok)
true

func CmpHasSuffix

func CmpHasSuffix(t TestingT, got interface{}, expected string, args ...interface{}) bool

CmpHasSuffix is a shortcut for:

td.Cmp(t, got, td.HasSuffix(expected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#HasSuffix for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "foobar"

ok := td.CmpHasSuffix(t, got, "bar", "checks %s", got)
fmt.Println("using string:", ok)

ok = td.Cmp(t, []byte(got), td.HasSuffix("bar"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.CmpHasSuffix(t, got, "bar", "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.CmpHasSuffix(t, got, "bar", "checks %s", got)
fmt.Println(ok)
true

func CmpIsa

func CmpIsa(t TestingT, got interface{}, model interface{}, args ...interface{}) bool

CmpIsa is a shortcut for:

td.Cmp(t, got, td.Isa(model), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Isa for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

type TstStruct struct {
	Field int
}

got := TstStruct{Field: 1}

ok := td.CmpIsa(t, got, TstStruct{}, "checks got is a TstStruct")
fmt.Println(ok)

ok = td.CmpIsa(t, got, &TstStruct{},
	"checks got is a pointer on a TstStruct")
fmt.Println(ok)

ok = td.CmpIsa(t, &got, &TstStruct{},
	"checks &got is a pointer on a TstStruct")
fmt.Println(ok)
true
false
true
Example (Interface)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.CmpIsa(t, got, (*fmt.Stringer)(nil),
	"checks got implements fmt.Stringer interface")
fmt.Println(ok)

errGot := fmt.Errorf("An error #%d occurred", 123)

ok = td.CmpIsa(t, errGot, (*error)(nil),
	"checks errGot is a *error or implements error interface")
fmt.Println(ok)

errGot = nil

ok = td.CmpIsa(t, errGot, (*error)(nil),
	"checks errGot is a *error or implements error interface")
fmt.Println(ok)

ok = td.CmpIsa(t, &errGot, (*error)(nil),
	"checks &errGot is a *error or implements error interface")
fmt.Println(ok)
true
true
false
true

func CmpJSON

func CmpJSON(t TestingT, got interface{}, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

CmpJSON is a shortcut for:

td.Cmp(t, got, td.JSON(expectedJSON, params...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#JSON for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Basic)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

ok := td.CmpJSON(t, got, `{"age":42,"fullname":"Bob"}`, nil)
fmt.Println("check got with age then fullname:", ok)

ok = td.CmpJSON(t, got, `{"fullname":"Bob","age":42}`, nil)
fmt.Println("check got with fullname then age:", ok)

ok = td.CmpJSON(t, got, `
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42     /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
}`, nil)
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = td.CmpJSON(t, got, `{"fullname":"Bob","age":42,"gender":"male"}`, nil)
fmt.Println("check got with gender field:", ok)

ok = td.CmpJSON(t, got, `{"fullname":"Bob"}`, nil)
fmt.Println("check got with fullname only:", ok)

ok = td.CmpJSON(t, true, `true`, nil)
fmt.Println("check boolean got is true:", ok)

ok = td.CmpJSON(t, 42, `42`, nil)
fmt.Println("check numeric got is 42:", ok)

got = nil
ok = td.CmpJSON(t, got, `null`, nil)
fmt.Println("check nil got is null:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got with gender field: false
check got with fullname only: false
check boolean got is true: true
check numeric got is 42: true
check nil got is null: true
Example (File)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender"
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := td.CmpJSON(t, got, filename, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = td.CmpJSON(t, got, file, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
}

ok := td.CmpJSON(t, got, `{"age": $1, "fullname": $2}`, []interface{}{42, "Bob Foobar"})
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = td.CmpJSON(t, got, `{"age": $1, "fullname": $2}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar")})
fmt.Println("check got with numeric placeholders:", ok)

ok = td.CmpJSON(t, got, `{"age": "$1", "fullname": "$2"}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar")})
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = td.CmpJSON(t, got, `{"age": $age, "fullname": $name}`, []interface{}{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar"))})
fmt.Println("check got with named placeholders:", ok)

ok = td.CmpJSON(t, got, `{"age": $^NotZero, "fullname": $^NotEmpty}`, nil)
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func CmpKeys

func CmpKeys(t TestingT, got interface{}, val interface{}, args ...interface{}) bool

CmpKeys is a shortcut for:

td.Cmp(t, got, td.Keys(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Keys for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

ok := td.CmpKeys(t, got, []string{"bar", "foo", "zip"})
fmt.Println("All sorted keys are found:", ok)

ok = td.CmpKeys(t, got, []string{"zip", "bar", "foo"})
fmt.Println("All unsorted keys are found:", ok)

ok = td.CmpKeys(t, got, td.Bag("zip", "bar", "foo"))
fmt.Println("All unsorted keys are found, with the help of Bag operator:", ok)

ok = td.CmpKeys(t, got, td.ArrayEach(td.Len(3)))
fmt.Println("Each key is 3 bytes long:", ok)
All sorted keys are found: true
All unsorted keys are found: false
All unsorted keys are found, with the help of Bag operator: true
Each key is 3 bytes long: true

func CmpLax

func CmpLax(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool

CmpLax is a shortcut for:

td.Cmp(t, got, td.Lax(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Lax for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

gotInt64 := int64(1234)
gotInt32 := int32(1235)

type myInt uint16
gotMyInt := myInt(1236)

expected := td.Between(1230, 1240)

ok := td.CmpLax(t, gotInt64, expected)
fmt.Println("int64 got between ints [1230 .. 1240]:", ok)

ok = td.CmpLax(t, gotInt32, expected)
fmt.Println("int32 got between ints [1230 .. 1240]:", ok)

ok = td.CmpLax(t, gotMyInt, expected)
fmt.Println("myInt got between ints [1230 .. 1240]:", ok)
int64 got between ints [1230 .. 1240]: true
int32 got between ints [1230 .. 1240]: true
myInt got between ints [1230 .. 1240]: true

func CmpLen

func CmpLen(t TestingT, got interface{}, expectedLen interface{}, args ...interface{}) bool

CmpLen is a shortcut for:

td.Cmp(t, got, td.Len(expectedLen), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Len for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := &testing.T{}

got := map[int]bool{11: true, 22: false, 33: false}

ok := td.CmpLen(t, got, 3, "checks %v len is 3", got)
fmt.Println(ok)

ok = td.CmpLen(t, got, 0, "checks %v len is 0", got)
fmt.Println(ok)

got = nil

ok = td.CmpLen(t, got, 0, "checks %v len is 0", got)
fmt.Println(ok)
true
false
true
Example (OperatorMap)

Code:

t := &testing.T{}

got := map[int]bool{11: true, 22: false, 33: false}

ok := td.CmpLen(t, got, td.Between(3, 8),
	"checks %v len is in [3 .. 8]", got)
fmt.Println(ok)

ok = td.CmpLen(t, got, td.Gte(3), "checks %v len is ≥ 3", got)
fmt.Println(ok)
true
true
Example (OperatorSlice)

Code:

t := &testing.T{}

got := []int{11, 22, 33}

ok := td.CmpLen(t, got, td.Between(3, 8),
	"checks %v len is in [3 .. 8]", got)
fmt.Println(ok)

ok = td.CmpLen(t, got, td.Lt(5), "checks %v len is < 5", got)
fmt.Println(ok)
true
true
Example (Slice)

Code:

t := &testing.T{}

got := []int{11, 22, 33}

ok := td.CmpLen(t, got, 3, "checks %v len is 3", got)
fmt.Println(ok)

ok = td.CmpLen(t, got, 0, "checks %v len is 0", got)
fmt.Println(ok)

got = nil

ok = td.CmpLen(t, got, 0, "checks %v len is 0", got)
fmt.Println(ok)
true
false
true

func CmpLt

func CmpLt(t TestingT, got interface{}, maxExpectedValue interface{}, args ...interface{}) bool

CmpLt is a shortcut for:

td.Cmp(t, got, td.Lt(maxExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Lt for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.CmpLt(t, got, 157, "checks %v is < 157", got)
fmt.Println(ok)

ok = td.CmpLt(t, got, 156, "checks %v is < 156", got)
fmt.Println(ok)
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.CmpLt(t, got, "abd", `checks "%v" is < "abd"`, got)
fmt.Println(ok)

ok = td.CmpLt(t, got, "abc", `checks "%v" is < "abc"`, got)
fmt.Println(ok)
true
false

func CmpLte

func CmpLte(t TestingT, got interface{}, maxExpectedValue interface{}, args ...interface{}) bool

CmpLte is a shortcut for:

td.Cmp(t, got, td.Lte(maxExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Lte for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.CmpLte(t, got, 156, "checks %v is ≤ 156", got)
fmt.Println(ok)

ok = td.CmpLte(t, got, 157, "checks %v is ≤ 157", got)
fmt.Println(ok)

ok = td.CmpLte(t, got, 155, "checks %v is ≤ 155", got)
fmt.Println(ok)
true
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.CmpLte(t, got, "abc", `checks "%v" is ≤ "abc"`, got)
fmt.Println(ok)

ok = td.CmpLte(t, got, "abd", `checks "%v" is ≤ "abd"`, got)
fmt.Println(ok)

ok = td.CmpLte(t, got, "abb", `checks "%v" is ≤ "abb"`, got)
fmt.Println(ok)
true
true
false

func CmpMap

func CmpMap(t TestingT, got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool

CmpMap is a shortcut for:

td.Cmp(t, got, td.Map(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Map for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := td.CmpMap(t, got, map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()},
	"checks map %v", got)
fmt.Println(ok)

ok = td.CmpMap(t, got, map[string]int{}, td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks map %v", got)
fmt.Println(ok)

ok = td.CmpMap(t, got, (map[string]int)(nil), td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks map %v", got)
fmt.Println(ok)
true
true
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := td.CmpMap(t, got, MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()},
	"checks typed map %v", got)
fmt.Println(ok)

ok = td.CmpMap(t, &got, &MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()},
	"checks pointer on typed map %v", got)
fmt.Println(ok)

ok = td.CmpMap(t, &got, &MyMap{}, td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks pointer on typed map %v", got)
fmt.Println(ok)

ok = td.CmpMap(t, &got, (*MyMap)(nil), td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks pointer on typed map %v", got)
fmt.Println(ok)
true
true
true
true

func CmpMapEach

func CmpMapEach(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool

CmpMapEach is a shortcut for:

td.Cmp(t, got, td.MapEach(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#MapEach for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := td.CmpMapEach(t, got, td.Between(10, 90),
	"checks each value of map %v is in [10 .. 90]", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := td.CmpMapEach(t, got, td.Between(10, 90),
	"checks each value of typed map %v is in [10 .. 90]", got)
fmt.Println(ok)

ok = td.CmpMapEach(t, &got, td.Between(10, 90),
	"checks each value of typed map pointer %v is in [10 .. 90]", got)
fmt.Println(ok)
true
true

func CmpN

func CmpN(t TestingT, got interface{}, num interface{}, tolerance interface{}, args ...interface{}) bool

CmpN is a shortcut for:

td.Cmp(t, got, td.N(num, tolerance), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#N for details.

N() optional parameter "tolerance" is here mandatory. 0 value should be passed to mimic its absence in original N() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := 1.12345

ok := td.CmpN(t, got, 1.1234, 0.00006,
	"checks %v = 1.1234 ± 0.00006", got)
fmt.Println(ok)
true

func CmpNaN

func CmpNaN(t TestingT, got interface{}, args ...interface{}) bool

CmpNaN is a shortcut for:

td.Cmp(t, got, td.NaN(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NaN for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Float32)

Code:

t := &testing.T{}

got := float32(math.NaN())

ok := td.CmpNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("float32(math.NaN()) is float32 not-a-number:", ok)

got = 12

ok = td.CmpNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("float32(12) is float32 not-a-number:", ok)
float32(math.NaN()) is float32 not-a-number: true
float32(12) is float32 not-a-number: false
Example (Float64)

Code:

t := &testing.T{}

got := math.NaN()

ok := td.CmpNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("math.NaN() is not-a-number:", ok)

got = 12

ok = td.CmpNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("float64(12) is not-a-number:", ok)

func CmpNil

func CmpNil(t TestingT, got interface{}, args ...interface{}) bool

CmpNil is a shortcut for:

td.Cmp(t, got, td.Nil(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Nil for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

var got fmt.Stringer // interface

ok := td.Cmp(t, got, nil)
fmt.Println(ok)

ok = td.CmpNil(t, got)
fmt.Println(ok)

got = (*bytes.Buffer)(nil)

ok = td.Cmp(t, got, nil)
fmt.Println(ok)

ok = td.CmpNil(t, got)
fmt.Println(ok)
true
true
false
true

func CmpNoError

func CmpNoError(t TestingT, got error, args ...interface{}) bool

CmpNoError checks that "got" is nil error.

value, err := MyFunction(1, 2, 3)
if td.CmpNoError(t, err) {
  // one can now check value...
}

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := fmt.Errorf("Error #%d", 42)
ok := td.CmpNoError(t, got, "An error occurred")
fmt.Println(ok)

got = nil
ok = td.CmpNoError(t, got, "An error occurred")
fmt.Println(ok)
false
true

func CmpNone

func CmpNone(t TestingT, got interface{}, notExpectedValues []interface{}, args ...interface{}) bool

CmpNone is a shortcut for:

td.Cmp(t, got, td.None(notExpectedValues...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#None for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := 18

ok := td.CmpNone(t, got, []interface{}{0, 10, 20, 30, td.Between(100, 199)},
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

got = 20

ok = td.CmpNone(t, got, []interface{}{0, 10, 20, 30, td.Between(100, 199)},
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

got = 142

ok = td.CmpNone(t, got, []interface{}{0, 10, 20, 30, td.Between(100, 199)},
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

prime := td.Flatten([]int{1, 2, 3, 5, 7, 11, 13})
even := td.Flatten([]int{2, 4, 6, 8, 10, 12, 14})
for _, got := range [...]int{9, 3, 8, 15} {
	ok = td.CmpNone(t, got, []interface{}{prime, even, td.Gt(14)},
		"checks %v is not prime number, nor an even number and not > 14")
	fmt.Printf("%d → %t\n", got, ok)
}
true
false
false
9 → true
3 → false
8 → false
15 → false

func CmpNot

func CmpNot(t TestingT, got interface{}, notExpected interface{}, args ...interface{}) bool

CmpNot is a shortcut for:

td.Cmp(t, got, td.Not(notExpected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Not for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := 42

ok := td.CmpNot(t, got, 0, "checks %v is non-null", got)
fmt.Println(ok)

ok = td.CmpNot(t, got, td.Between(10, 30),
	"checks %v is not in [10 .. 30]", got)
fmt.Println(ok)

got = 0

ok = td.CmpNot(t, got, 0, "checks %v is non-null", got)
fmt.Println(ok)
true
true
false

func CmpNotAny

func CmpNotAny(t TestingT, got interface{}, notExpectedItems []interface{}, args ...interface{}) bool

CmpNotAny is a shortcut for:

td.Cmp(t, got, td.NotAny(notExpectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotAny for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{4, 5, 9, 42}

ok := td.CmpNotAny(t, got, []interface{}{3, 6, 8, 41, 43},
	"checks %v contains no item listed in NotAny()", got)
fmt.Println(ok)

ok = td.CmpNotAny(t, got, []interface{}{3, 6, 8, 42, 43},
	"checks %v contains no item listed in NotAny()", got)
fmt.Println(ok)

notExpected := []int{3, 6, 8, 41, 43}
ok = td.CmpNotAny(t, got, []interface{}{td.Flatten(notExpected)},
	"checks %v contains no item listed in notExpected", got)
fmt.Println(ok)
true
false
true

func CmpNotEmpty

func CmpNotEmpty(t TestingT, got interface{}, args ...interface{}) bool

CmpNotEmpty is a shortcut for:

td.Cmp(t, got, td.NotEmpty(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotEmpty for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpNotEmpty(t, nil)
fmt.Println(ok)

ok = td.CmpNotEmpty(t, "foobar")
fmt.Println(ok)

ok = td.CmpNotEmpty(t, 0)
fmt.Println(ok)

ok = td.CmpNotEmpty(t, map[string]int{"foobar": 42})
fmt.Println(ok)

ok = td.CmpNotEmpty(t, []int{1})
fmt.Println(ok)

ok = td.CmpNotEmpty(t, [3]int{})
fmt.Println(ok)
false
true
false
true
true
true
Example (Pointers)

Code:

t := &testing.T{}

type MySlice []int

ok := td.CmpNotEmpty(t, MySlice{12})
fmt.Println(ok)

ok = td.CmpNotEmpty(t, &MySlice{12})
fmt.Println(ok)

l1 := &MySlice{12}
l2 := &l1
l3 := &l2
ok = td.CmpNotEmpty(t, &l3)
fmt.Println(ok)

// But not for others types as:
type MyStruct struct {
	Value int
}

ok = td.CmpNotEmpty(t, &MyStruct{})
fmt.Println(ok)
true
true
true
false

func CmpNotNaN

func CmpNotNaN(t TestingT, got interface{}, args ...interface{}) bool

CmpNotNaN is a shortcut for:

td.Cmp(t, got, td.NotNaN(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotNaN for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Float32)

Code:

t := &testing.T{}

got := float32(math.NaN())

ok := td.CmpNotNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("float32(math.NaN()) is NOT float32 not-a-number:", ok)

got = 12

ok = td.CmpNotNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("float32(12) is NOT float32 not-a-number:", ok)
float32(math.NaN()) is NOT float32 not-a-number: false
float32(12) is NOT float32 not-a-number: true
Example (Float64)

Code:

t := &testing.T{}

got := math.NaN()

ok := td.CmpNotNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("math.NaN() is not-a-number:", ok)

got = 12

ok = td.CmpNotNaN(t, got,
	"checks %v is not-a-number", got)

fmt.Println("float64(12) is not-a-number:", ok)

func CmpNotNil

func CmpNotNil(t TestingT, got interface{}, args ...interface{}) bool

CmpNotNil is a shortcut for:

td.Cmp(t, got, td.NotNil(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotNil for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

var got fmt.Stringer = &bytes.Buffer{}

ok := td.Cmp(t, got, td.Not(nil))
fmt.Println(ok)

ok = td.CmpNotNil(t, got)
fmt.Println(ok)

got = (*bytes.Buffer)(nil)

ok = td.Cmp(t, got, td.Not(nil))
fmt.Println(ok)

ok = td.CmpNotNil(t, got)
fmt.Println(ok)
true
true
true
false

func CmpNotPanic

func CmpNotPanic(t TestingT, fn func(), args ...interface{}) bool

CmpNotPanic calls "fn" and checks no panic() occurred. If a panic() occurred false is returned then the panic() parameter and the stack trace appear in the test report.

Note that calling panic(nil) in "fn" body is detected as a panic.

td.CmpNotPanic(t, func() {}) // succeeds as function does not panic

td.CmpNotPanic(t, func() { panic("I am panicking!") }) // fails
td.CmpNotPanic(t, func() { panic(nil) })               // fails too

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpNotPanic(t, func() {})
fmt.Println("checks a panic DID NOT occur:", ok)

ok = td.CmpNotPanic(t, func() { panic("I am panicking!") },
	"Hope it does not panic!")
fmt.Println("still no panic?", ok)

ok = td.CmpNotPanic(t, func() { panic(nil) }, "Checks for panic(nil)")
fmt.Println("last no panic?", ok)
checks a panic DID NOT occur: true
still no panic? false
last no panic? false

func CmpNotZero

func CmpNotZero(t TestingT, got interface{}, args ...interface{}) bool

CmpNotZero is a shortcut for:

td.Cmp(t, got, td.NotZero(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotZero for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpNotZero(t, 0)
fmt.Println(ok)

ok = td.CmpNotZero(t, float64(0))
fmt.Println(ok)

ok = td.CmpNotZero(t, 12)
fmt.Println(ok)

ok = td.CmpNotZero(t, (map[string]int)(nil))
fmt.Println(ok)

ok = td.CmpNotZero(t, map[string]int{})
fmt.Println(ok)

ok = td.CmpNotZero(t, ([]int)(nil))
fmt.Println(ok)

ok = td.CmpNotZero(t, []int{})
fmt.Println(ok)

ok = td.CmpNotZero(t, [3]int{})
fmt.Println(ok)

ok = td.CmpNotZero(t, [3]int{0, 1})
fmt.Println(ok)

ok = td.CmpNotZero(t, bytes.Buffer{})
fmt.Println(ok)

ok = td.CmpNotZero(t, &bytes.Buffer{})
fmt.Println(ok)

ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.NotZero()))
fmt.Println(ok)
false
false
true
false
true
false
true
false
true
false
true
false

func CmpPPtr

func CmpPPtr(t TestingT, got interface{}, val interface{}, args ...interface{}) bool

CmpPPtr is a shortcut for:

td.Cmp(t, got, td.PPtr(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#PPtr for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

num := 12
got := &num

ok := td.CmpPPtr(t, &got, 12)
fmt.Println(ok)

ok = td.CmpPPtr(t, &got, td.Between(4, 15))
fmt.Println(ok)
true
true

func CmpPanic

func CmpPanic(t TestingT, fn func(), expectedPanic interface{},
	args ...interface{}) bool

CmpPanic calls "fn" and checks a panic() occurred with the "expectedPanic" parameter. It returns true only if both conditions are fulfilled.

Note that calling panic(nil) in "fn" body is detected as a panic (in this case "expectedPanic" has to be nil).

td.CmpPanic(t,
  func() { panic("I am panicking!") },
  "I am panicking!",
  "The function should panic with the right string") // succeeds

td.CmpPanic(t,
  func() { panic("I am panicking!") },
  Contains("panicking!"),
  "The function should panic with a string containing `panicking!`") // succeeds

td.CmpPanic(t, func() { panic(nil) }, nil, "Checks for panic(nil)") // succeeds

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpPanic(t,
	func() { panic("I am panicking!") }, "I am panicking!",
	"Checks for panic")
fmt.Println("checks exact panic() string:", ok)

ok = td.CmpPanic(t,
	func() { panic("I am panicking!") }, td.Contains("panicking!"),
	"Checks for panic")
fmt.Println("checks panic() sub-string:", ok)

ok = td.CmpPanic(t, func() { panic(nil) }, nil, "Checks for panic(nil)")
fmt.Println("checks for panic(nil):", ok)

// As well as structured data panic
type PanicStruct struct {
	Error string
	Code  int
}

ok = td.CmpPanic(t,
	func() {
		panic(PanicStruct{Error: "Memory violation", Code: 11})
	},
	PanicStruct{
		Error: "Memory violation",
		Code:  11,
	})
fmt.Println("checks exact panic() struct:", ok)

ok = td.CmpPanic(t,
	func() {
		panic(PanicStruct{Error: "Memory violation", Code: 11})
	},
	td.Struct(PanicStruct{}, td.StructFields{
		"Code": td.Between(10, 20),
	}))
fmt.Println("checks panic() struct against TestDeep operators:", ok)

ok = td.CmpPanic(t, func() {}, nil)
fmt.Println("checks a panic occurred:", ok)
checks exact panic() string: true
checks panic() sub-string: true
checks for panic(nil): true
checks exact panic() struct: true
checks panic() struct against TestDeep operators: true
checks a panic occurred: false

func CmpPtr

func CmpPtr(t TestingT, got interface{}, val interface{}, args ...interface{}) bool

CmpPtr is a shortcut for:

td.Cmp(t, got, td.Ptr(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Ptr for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := 12

ok := td.CmpPtr(t, &got, 12)
fmt.Println(ok)

ok = td.CmpPtr(t, &got, td.Between(4, 15))
fmt.Println(ok)
true
true

func CmpRe

func CmpRe(t TestingT, got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool

CmpRe is a shortcut for:

td.Cmp(t, got, td.Re(reg, capture), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Re for details.

Re() optional parameter "capture" is here mandatory. nil value should be passed to mimic its absence in original Re() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "foo bar"
ok := td.CmpRe(t, got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)

got = "bar foo"
ok = td.CmpRe(t, got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)
true
false
Example (Capture)

Code:

t := &testing.T{}

got := "foo bar biz"
ok := td.CmpRe(t, got, `^(\w+) (\w+) (\w+)$`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo bar! biz"
ok = td.CmpRe(t, got, `^(\w+) (\w+) (\w+)$`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (Compiled)

Code:

t := &testing.T{}

expected := regexp.MustCompile("(zip|bar)$")

got := "foo bar"
ok := td.CmpRe(t, got, expected, nil, "checks value %s", got)
fmt.Println(ok)

got = "bar foo"
ok = td.CmpRe(t, got, expected, nil, "checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCapture)

Code:

t := &testing.T{}

expected := regexp.MustCompile(`^(\w+) (\w+) (\w+)$`)

got := "foo bar biz"
ok := td.CmpRe(t, got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo bar! biz"
ok = td.CmpRe(t, got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledError)

Code:

t := &testing.T{}

expected := regexp.MustCompile("(zip|bar)$")

got := errors.New("foo bar")
ok := td.CmpRe(t, got, expected, nil, "checks value %s", got)
fmt.Println(ok)
true
Example (CompiledStringer)

Code:

t := &testing.T{}

expected := regexp.MustCompile("(zip|bar)$")

got := bytes.NewBufferString("foo bar")
ok := td.CmpRe(t, got, expected, nil, "checks value %s", got)
fmt.Println(ok)
true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foo bar")
ok := td.CmpRe(t, got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foo bar")
ok := td.CmpRe(t, got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)
true

func CmpReAll

func CmpReAll(t TestingT, got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool

CmpReAll is a shortcut for:

td.Cmp(t, got, td.ReAll(reg, capture), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ReAll for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Capture)

Code:

t := &testing.T{}

got := "foo bar biz"
ok := td.CmpReAll(t, got, `(\w+)`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo BAR biz"
ok = td.CmpReAll(t, got, `(\w+)`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CaptureComplex)

Code:

t := &testing.T{}

got := "11 45 23 56 85 96"
ok := td.CmpReAll(t, got, `(\d+)`, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 10 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)

ok = td.CmpReAll(t, got, `(\d+)`, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 20 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCapture)

Code:

t := &testing.T{}

expected := regexp.MustCompile(`(\w+)`)

got := "foo bar biz"
ok := td.CmpReAll(t, got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo BAR biz"
ok = td.CmpReAll(t, got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCaptureComplex)

Code:

t := &testing.T{}

expected := regexp.MustCompile(`(\d+)`)

got := "11 45 23 56 85 96"
ok := td.CmpReAll(t, got, expected, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 10 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)

ok = td.CmpReAll(t, got, expected, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 20 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)
true
false

func CmpSStruct

func CmpSStruct(t TestingT, got interface{}, model interface{}, expectedFields StructFields, args ...interface{}) bool

CmpSStruct is a shortcut for:

td.Cmp(t, got, td.SStruct(model, expectedFields), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SStruct for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

type Person struct {
	Name        string
	Age         int
	NumChildren int
}

got := Person{
	Name:        "Foobar",
	Age:         42,
	NumChildren: 0,
}

ok := td.CmpSStruct(t, got, Person{Name: "Foobar"}, td.StructFields{
	"Age": td.Between(40, 50),
},
	"checks %v is the right Person")
fmt.Println(ok)

got.NumChildren = 3
ok = td.CmpSStruct(t, got, Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.CmpSStruct(t, &got, &Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.CmpSStruct(t, &got, (*Person)(nil), td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)
true
true
true
true

func CmpSet

func CmpSet(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool

CmpSet is a shortcut for:

td.Cmp(t, got, td.Set(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Set for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.CmpSet(t, got, []interface{}{1, 2, 3, 5, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.CmpSet(t, got, []interface{}{1, 2, 2, 2, 2, 2, 3, 5, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.CmpSet(t, got, []interface{}{td.Between(1, 4), 3, td.Between(2, 10)},
	"checks all items are present, in any order")
fmt.Println(ok)

expected := []int{1, 2, 3, 5, 8}
ok = td.CmpSet(t, got, []interface{}{td.Flatten(expected)},
	"checks all expected items are present, in any order")
fmt.Println(ok)
true
true
true
true

func CmpShallow

func CmpShallow(t TestingT, got interface{}, expectedPtr interface{}, args ...interface{}) bool

CmpShallow is a shortcut for:

td.Cmp(t, got, td.Shallow(expectedPtr), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Shallow for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

type MyStruct struct {
	Value int
}
data := MyStruct{Value: 12}
got := &data

ok := td.CmpShallow(t, got, &data,
	"checks pointers only, not contents")
fmt.Println(ok)

ok = td.CmpShallow(t, got, &MyStruct{Value: 12},
	"checks pointers only, not contents")
fmt.Println(ok)
true
false
Example (Slice)

Code:

t := &testing.T{}

back := []int{1, 2, 3, 1, 2, 3}
a := back[:3]
b := back[3:]

ok := td.CmpShallow(t, a, back)
fmt.Println("are ≠ but share the same area:", ok)

ok = td.CmpShallow(t, b, back)
fmt.Println("are = but do not point to same area:", ok)
are ≠ but share the same area: true
are = but do not point to same area: false
Example (String)

Code:

t := &testing.T{}

back := "foobarfoobar"
a := back[:6]
b := back[6:]

ok := td.CmpShallow(t, a, back)
fmt.Println("are ≠ but share the same area:", ok)

ok = td.CmpShallow(t, b, a)
fmt.Println("are = but do not point to same area:", ok)
are ≠ but share the same area: true
are = but do not point to same area: false

func CmpSlice

func CmpSlice(t TestingT, got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool

CmpSlice is a shortcut for:

td.Cmp(t, got, td.Slice(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Slice for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Slice)

Code:

t := &testing.T{}

got := []int{42, 58, 26}

ok := td.CmpSlice(t, got, []int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks slice %v", got)
fmt.Println(ok)

ok = td.CmpSlice(t, got, []int{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks slice %v", got)
fmt.Println(ok)

ok = td.CmpSlice(t, got, ([]int)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks slice %v", got)
fmt.Println(ok)
true
true
true
Example (TypedSlice)

Code:

t := &testing.T{}

type MySlice []int

got := MySlice{42, 58, 26}

ok := td.CmpSlice(t, got, MySlice{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks typed slice %v", got)
fmt.Println(ok)

ok = td.CmpSlice(t, &got, &MySlice{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks pointer on typed slice %v", got)
fmt.Println(ok)

ok = td.CmpSlice(t, &got, &MySlice{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed slice %v", got)
fmt.Println(ok)

ok = td.CmpSlice(t, &got, (*MySlice)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed slice %v", got)
fmt.Println(ok)
true
true
true
true

func CmpSmuggle

func CmpSmuggle(t TestingT, got interface{}, fn interface{}, expectedValue interface{}, args ...interface{}) bool

CmpSmuggle is a shortcut for:

td.Cmp(t, got, td.Smuggle(fn, expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Smuggle for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Auto_unmarshal)

Code:

t := &testing.T{}

got := []byte(`{"a":1,"b":2}`)

ok := td.CmpSmuggle(t, got, func(b json.RawMessage) (r map[string]int, err error) {
	err = json.Unmarshal(b, &r)
	return
}, map[string]int{
	"a": 1,
	"b": 2,
})
fmt.Println("JSON contents is OK:", ok)
JSON contents is OK: true
Example (Complex)

Code:

t := &testing.T{}

// No end date but a start date and a duration
type StartDuration struct {
	StartDate time.Time
	Duration  time.Duration
}

for _, duration := range []time.Duration{48, 72, 96} {
	got := StartDuration{
		StartDate: time.Date(2018, time.February, 14, 12, 13, 14, 0, time.UTC),
		Duration:  duration * time.Hour,
	}

	ok := td.CmpSmuggle(t, got, func(sd StartDuration) time.Time {
		return sd.StartDate.Add(sd.Duration)
	}, td.Between(
		time.Date(2018, time.February, 17, 0, 0, 0, 0, time.UTC),
		time.Date(2018, time.February, 19, 0, 0, 0, 0, time.UTC)))
	fmt.Println(ok)

	ok = td.CmpSmuggle(t, got, func(sd StartDuration) td.SmuggledGot {
		return td.SmuggledGot{
			Name: "ComputedEndDate",
			Got:  sd.StartDate.Add(sd.Duration),
		}
	}, td.Between(
		time.Date(2018, time.February, 17, 0, 0, 0, 0, time.UTC),
		time.Date(2018, time.February, 19, 0, 0, 0, 0, time.UTC)))
	fmt.Println(ok)
}
false
false
true
true
true
true
Example (Convert)

Code:

t := &testing.T{}

got := int64(123)

ok := td.CmpSmuggle(t, got, func(n int64) int { return int(n) }, 123,
	"checks int64 got against an int value")
fmt.Println(ok)

ok = td.CmpSmuggle(t, "123", func(numStr string) (int, bool) {
	n, err := strconv.Atoi(numStr)
	return n, err == nil
}, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = td.CmpSmuggle(t, "123", func(numStr string) (int, bool, string) {
	n, err := strconv.Atoi(numStr)
	if err != nil {
		return 0, false, "string must contain a number"
	}
	return n, true, ""
}, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = td.CmpSmuggle(t, "123", func(numStr string) (int, error) {
	return strconv.Atoi(numStr)
}, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = td.CmpSmuggle(t, "123", strconv.Atoi, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)
true
true
true
true
true
Example (Field_path)

Code:

t := &testing.T{}

type Body struct {
	Name  string
	Value interface{}
}
type Request struct {
	Body *Body
}
type Transaction struct {
	Request
}
type ValueNum struct {
	Num int
}

got := &Transaction{
	Request: Request{
		Body: &Body{
			Name:  "test",
			Value: &ValueNum{Num: 123},
		},
	},
}

ok := td.CmpSmuggle(t, got, func(t *Transaction) (int, error) {
	if t.Request.Body == nil ||
		t.Request.Body.Value == nil {
		return 0, errors.New("Request.Body or Request.Body.Value is nil")
	}
	if v, ok := t.Request.Body.Value.(*ValueNum); ok && v != nil {
		return v.Num, nil
	}
	return 0, errors.New("Request.Body.Value isn't *ValueNum or nil")
}, td.Between(100, 200))
fmt.Println("check Num by hand:", ok)

ok = td.CmpSmuggle(t, got, "Request.Body.Value.Num", td.Between(100, 200))
fmt.Println("check Num using a fields-path:", ok)

ok = td.CmpSmuggle(t, got, "Body.Value.Num", td.Between(100, 200))
fmt.Println("check Num using an other fields-path:", ok)
check Num by hand: true
check Num using a fields-path: true
check Num using an other fields-path: true
Example (Interface)

Code:

t := &testing.T{}

gotTime, err := time.Parse(time.RFC3339, "2018-05-23T12:13:14Z")
if err != nil {
	t.Fatal(err)
}

ok := td.CmpSmuggle(t, gotTime, func(s fmt.Stringer) string {
	return s.String()
}, "2018-05-23 12:13:14 +0000 UTC")
fmt.Println("stringified time.Time OK:", ok)

// If got does not implement the fmt.Stringer interface, it fails
// without calling the Smuggle func
type MyTime time.Time
ok = td.CmpSmuggle(t, MyTime(gotTime), func(s fmt.Stringer) string {
	fmt.Println("Smuggle func called!")
	return s.String()
}, "2018-05-23 12:13:14 +0000 UTC")
fmt.Println("stringified MyTime OK:", ok)
Example (Lax)

Code:

t := &testing.T{}

got := int(123)

ok := td.CmpSmuggle(t, got, func(n int64) uint32 { return uint32(n) }, uint32(123))
fmt.Println("got int16(123) → smuggle via int64 → uint32(123):", ok)
got int16(123) → smuggle via int64 → uint32(123): true

func CmpString

func CmpString(t TestingT, got interface{}, expected string, args ...interface{}) bool

CmpString is a shortcut for:

td.Cmp(t, got, td.String(expected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#String for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := "foobar"

ok := td.CmpString(t, got, "foobar", "checks %s", got)
fmt.Println("using string:", ok)

ok = td.Cmp(t, []byte(got), td.String("foobar"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.CmpString(t, got, "foobar", "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.CmpString(t, got, "foobar", "checks %s", got)
fmt.Println(ok)
true

func CmpStruct

func CmpStruct(t TestingT, got interface{}, model interface{}, expectedFields StructFields, args ...interface{}) bool

CmpStruct is a shortcut for:

td.Cmp(t, got, td.Struct(model, expectedFields), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Struct for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

type Person struct {
	Name        string
	Age         int
	NumChildren int
}

got := Person{
	Name:        "Foobar",
	Age:         42,
	NumChildren: 3,
}

ok := td.CmpStruct(t, got, Person{Name: "Foobar"}, td.StructFields{
	"Age": td.Between(40, 50),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.CmpStruct(t, got, Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.CmpStruct(t, &got, &Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.CmpStruct(t, &got, (*Person)(nil), td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)
true
true
true
true

func CmpSubBagOf

func CmpSubBagOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool

CmpSubBagOf is a shortcut for:

td.Cmp(t, got, td.SubBagOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubBagOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.CmpSubBagOf(t, got, []interface{}{0, 0, 1, 1, 2, 2, 3, 3, 5, 5, 8, 8, 9, 9},
	"checks at least all items are present, in any order")
fmt.Println(ok)

ok = td.CmpSubBagOf(t, got, []interface{}{0, 0, 1, 1, 2, 2, 3, 3, 5, 5, 8, 9, 9},
	"checks at least all items are present, in any order")
fmt.Println(ok)

got = []int{1, 3, 5, 2}

ok = td.CmpSubBagOf(t, got, []interface{}{td.Between(0, 3), td.Between(0, 3), td.Between(0, 3), td.Between(0, 3), td.Gt(4), td.Gt(4)},
	"checks at least all items match, in any order with TestDeep operators")
fmt.Println(ok)

expected := []int{1, 2, 3, 5, 9, 8}
ok = td.CmpSubBagOf(t, got, []interface{}{td.Flatten(expected)},
	"checks at least all expected items are present, in any order")
fmt.Println(ok)
true
false
true
true

func CmpSubJSONOf

func CmpSubJSONOf(t TestingT, got interface{}, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

CmpSubJSONOf is a shortcut for:

td.Cmp(t, got, td.SubJSONOf(expectedJSON, params...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubJSONOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Basic)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

ok := td.CmpSubJSONOf(t, got, `{"age":42,"fullname":"Bob","gender":"male"}`, nil)
fmt.Println("check got with age then fullname:", ok)

ok = td.CmpSubJSONOf(t, got, `{"fullname":"Bob","age":42,"gender":"male"}`, nil)
fmt.Println("check got with fullname then age:", ok)

ok = td.CmpSubJSONOf(t, got, `
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42,    /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
  "gender":   "male" // This field is ignored as SubJSONOf
}`, nil)
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = td.CmpSubJSONOf(t, got, `{"fullname":"Bob","gender":"male"}`, nil)
fmt.Println("check got without age field:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got without age field: false
Example (File)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender",
  "details":  {
    "city": "TestCity",
    "zip":  666
  }
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := td.CmpSubJSONOf(t, got, filename, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = td.CmpSubJSONOf(t, got, file, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
}

ok := td.CmpSubJSONOf(t, got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{42, "Bob Foobar", "male"})
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = td.CmpSubJSONOf(t, got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with numeric placeholders:", ok)

ok = td.CmpSubJSONOf(t, got, `{"age": "$1", "fullname": "$2", "gender": "$3"}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = td.CmpSubJSONOf(t, got, `{"age": $age, "fullname": $name, "gender": $gender}`, []interface{}{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar")), td.Tag("gender", td.NotEmpty())})
fmt.Println("check got with named placeholders:", ok)

ok = td.CmpSubJSONOf(t, got, `{"age": $^NotZero, "fullname": $^NotEmpty, "gender": $^NotEmpty}`, nil)
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func CmpSubMapOf

func CmpSubMapOf(t TestingT, got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool

CmpSubMapOf is a shortcut for:

td.Cmp(t, got, td.SubMapOf(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubMapOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42}

ok := td.CmpSubMapOf(t, got, map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666},
	"checks map %v is included in expected keys/values", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42}

ok := td.CmpSubMapOf(t, got, MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666},
	"checks typed map %v is included in expected keys/values", got)
fmt.Println(ok)

ok = td.CmpSubMapOf(t, &got, &MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666},
	"checks pointed typed map %v is included in expected keys/values", got)
fmt.Println(ok)
true
true

func CmpSubSetOf

func CmpSubSetOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool

CmpSubSetOf is a shortcut for:

td.Cmp(t, got, td.SubSetOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubSetOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.CmpSubSetOf(t, got, []interface{}{1, 2, 3, 4, 5, 6, 7, 8},
	"checks at least all items are present, in any order, ignoring duplicates")
fmt.Println(ok)

ok = td.CmpSubSetOf(t, got, []interface{}{td.Between(1, 4), 3, td.Between(2, 10), td.Gt(100)},
	"checks at least all items are present, in any order, ignoring duplicates")
fmt.Println(ok)

expected := []int{1, 2, 3, 4, 5, 6, 7, 8}
ok = td.CmpSubSetOf(t, got, []interface{}{td.Flatten(expected)},
	"checks at least all expected items are present, in any order, ignoring duplicates")
fmt.Println(ok)
true
true
true

func CmpSuperBagOf

func CmpSuperBagOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool

CmpSuperBagOf is a shortcut for:

td.Cmp(t, got, td.SuperBagOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperBagOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.CmpSuperBagOf(t, got, []interface{}{8, 5, 8},
	"checks the items are present, in any order")
fmt.Println(ok)

ok = td.CmpSuperBagOf(t, got, []interface{}{td.Gt(5), td.Lte(2)},
	"checks at least 2 items of %v match", got)
fmt.Println(ok)

expected := []int{8, 5, 8}
ok = td.CmpSuperBagOf(t, got, []interface{}{td.Flatten(expected)},
	"checks the expected items are present, in any order")
fmt.Println(ok)
true
true
true

func CmpSuperJSONOf

func CmpSuperJSONOf(t TestingT, got interface{}, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

CmpSuperJSONOf is a shortcut for:

td.Cmp(t, got, td.SuperJSONOf(expectedJSON, params...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperJSONOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Basic)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

ok := td.CmpSuperJSONOf(t, got, `{"age":42,"fullname":"Bob","gender":"male"}`, nil)
fmt.Println("check got with age then fullname:", ok)

ok = td.CmpSuperJSONOf(t, got, `{"fullname":"Bob","age":42,"gender":"male"}`, nil)
fmt.Println("check got with fullname then age:", ok)

ok = td.CmpSuperJSONOf(t, got, `
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42,    /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
  "gender":   "male" // The gender!
}`, nil)
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = td.CmpSuperJSONOf(t, got, `{"fullname":"Bob","gender":"male","details":{}}`, nil)
fmt.Println("check got with details field:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got with details field: false
Example (File)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender"
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := td.CmpSuperJSONOf(t, got, filename, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = td.CmpSuperJSONOf(t, got, file, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

ok := td.CmpSuperJSONOf(t, got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{42, "Bob Foobar", "male"})
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = td.CmpSuperJSONOf(t, got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with numeric placeholders:", ok)

ok = td.CmpSuperJSONOf(t, got, `{"age": "$1", "fullname": "$2", "gender": "$3"}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = td.CmpSuperJSONOf(t, got, `{"age": $age, "fullname": $name, "gender": $gender}`, []interface{}{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar")), td.Tag("gender", td.NotEmpty())})
fmt.Println("check got with named placeholders:", ok)

ok = td.CmpSuperJSONOf(t, got, `{"age": $^NotZero, "fullname": $^NotEmpty, "gender": $^NotEmpty}`, nil)
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func CmpSuperMapOf

func CmpSuperMapOf(t TestingT, got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool

CmpSuperMapOf is a shortcut for:

td.Cmp(t, got, td.SuperMapOf(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperMapOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := td.CmpSuperMapOf(t, got, map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15)},
	"checks map %v contains at leat all expected keys/values", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := td.CmpSuperMapOf(t, got, MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15)},
	"checks typed map %v contains at leat all expected keys/values", got)
fmt.Println(ok)

ok = td.CmpSuperMapOf(t, &got, &MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15)},
	"checks pointed typed map %v contains at leat all expected keys/values",
	got)
fmt.Println(ok)
true
true

func CmpSuperSetOf

func CmpSuperSetOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool

CmpSuperSetOf is a shortcut for:

td.Cmp(t, got, td.SuperSetOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperSetOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.CmpSuperSetOf(t, got, []interface{}{1, 2, 3},
	"checks the items are present, in any order and ignoring duplicates")
fmt.Println(ok)

ok = td.CmpSuperSetOf(t, got, []interface{}{td.Gt(5), td.Lte(2)},
	"checks at least 2 items of %v match ignoring duplicates", got)
fmt.Println(ok)

expected := []int{1, 2, 3}
ok = td.CmpSuperSetOf(t, got, []interface{}{td.Flatten(expected)},
	"checks the expected items are present, in any order and ignoring duplicates")
fmt.Println(ok)
true
true
true

func CmpTrue

func CmpTrue(t TestingT, got bool, args ...interface{}) bool

CmpTrue is a shortcut for:

td.Cmp(t, got, true, args...)

Returns true if the test is OK, false if it fails.

td.CmpTrue(t, IsAvailable(x), "x should be available")

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := true
ok := td.CmpTrue(t, got, "check that got is true!")
fmt.Println(ok)

got = false
ok = td.CmpTrue(t, got, "check that got is true!")
fmt.Println(ok)
true
false

func CmpTruncTime

func CmpTruncTime(t TestingT, got interface{}, expectedTime interface{}, trunc time.Duration, args ...interface{}) bool

CmpTruncTime is a shortcut for:

td.Cmp(t, got, td.TruncTime(expectedTime, trunc), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#TruncTime for details.

TruncTime() optional parameter "trunc" is here mandatory. 0 value should be passed to mimic its absence in original TruncTime() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

dateToTime := func(str string) time.Time {
	t, err := time.Parse(time.RFC3339Nano, str)
	if err != nil {
		panic(err)
	}
	return t
}

got := dateToTime("2018-05-01T12:45:53.123456789Z")

expected := dateToTime("2018-05-01T12:45:53Z")
ok := td.CmpTruncTime(t, got, expected, time.Second,
	"checks date %v, truncated to the second", got)
fmt.Println(ok)

expected = dateToTime("2018-05-01T11:22:33.444444444Z")
ok = td.CmpTruncTime(t, got, expected, 24*time.Hour,
	"checks date %v, truncated to the day", got)
fmt.Println(ok)

expected = dateToTime("2018-05-01T12:45:53.123456789Z")
ok = td.CmpTruncTime(t, got, expected, 0,
	"checks date %v ignoring monotonic part", got)
fmt.Println(ok)
true
true
true

func CmpValues

func CmpValues(t TestingT, got interface{}, val interface{}, args ...interface{}) bool

CmpValues is a shortcut for:

td.Cmp(t, got, td.Values(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Values for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

ok := td.CmpValues(t, got, []int{1, 2, 3})
fmt.Println("All sorted values are found:", ok)

ok = td.CmpValues(t, got, []int{3, 1, 2})
fmt.Println("All unsorted values are found:", ok)

ok = td.CmpValues(t, got, td.Bag(3, 1, 2))
fmt.Println("All unsorted values are found, with the help of Bag operator:", ok)

ok = td.CmpValues(t, got, td.ArrayEach(td.Between(1, 3)))
fmt.Println("Each value is between 1 and 3:", ok)
All sorted values are found: true
All unsorted values are found: false
All unsorted values are found, with the help of Bag operator: true
Each value is between 1 and 3: true

func CmpZero

func CmpZero(t TestingT, got interface{}, args ...interface{}) bool

CmpZero is a shortcut for:

td.Cmp(t, got, td.Zero(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Zero for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := &testing.T{}

ok := td.CmpZero(t, 0)
fmt.Println(ok)

ok = td.CmpZero(t, float64(0))
fmt.Println(ok)

ok = td.CmpZero(t, 12)
fmt.Println(ok)

ok = td.CmpZero(t, (map[string]int)(nil))
fmt.Println(ok)

ok = td.CmpZero(t, map[string]int{})
fmt.Println(ok)

ok = td.CmpZero(t, ([]int)(nil))
fmt.Println(ok)

ok = td.CmpZero(t, []int{})
fmt.Println(ok)

ok = td.CmpZero(t, [3]int{})
fmt.Println(ok)

ok = td.CmpZero(t, [3]int{0, 1})
fmt.Println(ok)

ok = td.CmpZero(t, bytes.Buffer{})
fmt.Println(ok)

ok = td.CmpZero(t, &bytes.Buffer{})
fmt.Println(ok)

ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.Zero()))
fmt.Println(ok)
true
true
false
true
false
true
false
true
false
true
false
true

func EqDeeply

func EqDeeply(got, expected interface{}) bool

EqDeeply returns true if "got" matches "expected". "expected" can be the same type as "got" is, or contains some TestDeep operators.

got := "foobar"
td.EqDeeply(got, "foobar")            // returns true
td.EqDeeply(got, td.HasPrefix("foo")) // returns true
Example

Code:

type MyStruct struct {
	Name  string
	Num   int
	Items []int
}

got := &MyStruct{
	Name:  "Foobar",
	Num:   12,
	Items: []int{4, 5, 9, 3, 8},
}

if td.EqDeeply(got,
	td.Struct(&MyStruct{},
		td.StructFields{
			"Name":  td.Re("^Foo"),
			"Num":   td.Between(10, 20),
			"Items": td.ArrayEach(td.Between(3, 9)),
		})) {
	fmt.Println("Match!")
} else {
	fmt.Println("NO!")
}
Match!

func EqDeeplyError

func EqDeeplyError(got, expected interface{}) error

EqDeeplyError returns nil if "got" matches "expected". "expected" can be the same type as got is, or contains some TestDeep operators. If "got" does not match "expected", the returned *ctxerr.Error contains the reason of the first mismatch detected.

got := "foobar"
if err := td.EqDeeplyError(got, "foobar"); err != nil {
  // …
}
if err := td.EqDeeplyError(got, td.HasPrefix("foo")); err != nil {
  // …
}
Example

Code:

type MyStruct struct {
	Name  string
	Num   int
	Items []int
}

got := &MyStruct{
	Name:  "Foobar",
	Num:   12,
	Items: []int{4, 5, 9, 3, 8},
}

err := td.EqDeeplyError(got,
	td.Struct(&MyStruct{},
		td.StructFields{
			"Name":  td.Re("^Foo"),
			"Num":   td.Between(10, 20),
			"Items": td.ArrayEach(td.Between(3, 8)),
		}))
if err != nil {
	fmt.Println(err)
}
DATA.Items[2]: values differ
	     got: 9
	expected: 3 ≤ got ≤ 8
[under TestDeep operator Between at example.go:18]

func Flatten

func Flatten(slice interface{}) flat.Slice

Flatten allows to flatten any slice or array in parameters of operators expecting ...interface{}.

For example the Set operator is defined as:

func Set(expectedItems ...interface{}) TestDeep

so when comparing to a []int slice, we usually do:

got := []int{42, 66, 22}
td.Cmp(t, got, td.Set(22, 42, 66))

it works but if the expected items are already in a []int, we have to copy them in a []interface{} as it can not be flattened directly in Set parameters:

expected := []int{22, 42, 66}
expectedIf := make([]interface{}, len(expected))
for i, item := range expected {
  expectedIf[i] = item
}
td.Cmp(t, got, td.Set(expectedIf...))

but it is a bit boring and less efficient, as Set does not keep the []interface{} behind the scene.

The same with Flatten follows:

expected := []int{22, 42, 66}
td.Cmp(t, got, td.Set(td.Flatten(expected)))

Several Flatten calls can be passed, and even combined with normal parameters:

expectedPart1 := []int{11, 22, 33}
expectedPart2 := []int{55, 66, 77}
expectedPart3 := []int{99}
td.Cmp(t, got,
  td.Set(
    td.Flatten(expectedPart1),
    44,
    td.Flatten(expectedPart2),
    88,
    td.Flatten(expectedPart3),
  ))

is exactly the same as:

td.Cmp(t, got, td.Set(11, 22, 33, 44, 55, 66, 77, 88, 99))

Note that Flatten calls can even be nested:

td.Cmp(t, got,
  td.Set(
    td.Flatten([]interface{}{
      11,
      td.Flatten([]int{22, 33}),
      td.Flatten([]int{44, 55, 66}),
    }),
    77,
  ))

is exactly the same as:

td.Cmp(t, got, td.Set(11, 22, 33, 44, 55, 66, 77))

type ArrayEntries

type ArrayEntries map[int]interface{}

ArrayEntries allows to pass array or slice entries to check in functions Array and Slice. It is a map whose each key is the item index and the corresponding value the expected item value (which can be a TestDeep operator as well as a zero value).

type BoundsKind

type BoundsKind uint8

BoundsKind type qualifies the "Between" bounds.

const (
	// BoundsInIn allows to match between "from" and "to" both included
	BoundsInIn BoundsKind = iota
	// BoundsInOut allows to match between "from" included and "to" excluded
	BoundsInOut
	// BoundsOutIn allows to match between "from" excluded and "to" included
	BoundsOutIn
	// BoundsOutOut allows to match between "from" and "to" both excluded
	BoundsOutOut
)

type ContextConfig

type ContextConfig struct {
	// RootName is the string used to represent the root of got data. It
	// defaults to "DATA". For an HTTP response body, it could be "BODY"
	// for example.
	RootName string
	// MaxErrors is the maximal number of errors to dump in case of Cmp*
	// failure.
	//
	// It defaults to 10 except if the environment variable
	// TESTDEEP_MAX_ERRORS is set. In this latter case, the
	// TESTDEEP_MAX_ERRORS value is converted to an int and used as is.
	//
	// Setting it to 0 has the same effect as 1: only the first error
	// will be dumped without the "Too many errors" error.
	//
	// Setting it to a negative number means no limit: all errors
	// will be dumped.
	MaxErrors int

	// FailureIsFatal allows to Fatal() (instead of Error()) when a test
	// fails. Using *testing.T or *testing.B instance as t.TB value, FailNow()
	// is called behind the scenes when Fatal() is called. See testing
	// documentation for details.
	FailureIsFatal bool
	// UseEqual allows to use the Equal method on got (if it exists) or
	// on any of its component to compare got and expected values.
	//
	// The signature of the Equal method should be:
	//   (A) Equal(B) bool
	// with B assignable to A.
	//
	// See time.Time as an example of accepted Equal() method.
	UseEqual bool
	// BeLax allows to compare different but convertible types. If set
	// to false (default), got and expected types must be the same. If
	// set to true and expected type is convertible to got one, expected
	// is first converted to go type before its comparison. See CmpLax
	// function/method and Lax operator to set this flag without
	// providing a specific configuration.
	BeLax bool
	// contains filtered or unexported fields
}

ContextConfig allows to configure finely how tests failures are rendered.

See NewT function to use it.

func (ContextConfig) Equal

func (c ContextConfig) Equal(o ContextConfig) bool

Equal returns true if both ContextConfig are equal. Only public fields are taken into account to check equality.

type MapEntries

type MapEntries map[interface{}]interface{}

MapEntries allows to pass map entries to check in function Map. It is a map whose each key is the expected entry key and the corresponding value the expected entry value (which can be a TestDeep operator as well as a zero value.)

type SmuggledGot

type SmuggledGot struct {
	Name string
	Got  interface{}
}

SmuggledGot can be returned by a Smuggle function to name the transformed / returned value.

type StructFields

type StructFields map[string]interface{}

StructFields allows to pass struct fields to check in functions Struct and SStruct. It is a map whose each key is the expected field name and the corresponding value the expected field value (which can be a TestDeep operator as well as a zero value.)

type T

type T struct {
	testing.TB
	Config ContextConfig // defaults to DefaultContextConfig
}

T is a type that encapsulates testing.TB interface (which is implemented by *testing.T and *testing.B) allowing to easily use *testing.T methods as well as T ones.

func Assert

func Assert(t testing.TB, config ...ContextConfig) *T

Assert return a new T instance with FailureIsFatal flag set to false.

assert := Assert(t)

is roughly equivalent to:

assert := NewT(t).FailureIsFatal(false)

See NewT documentation for usefulness of "config" optional parameter.

func NewT

func NewT(t testing.TB, config ...ContextConfig) *T

NewT returns a new T instance. Typically used as:

import (
  "testing"

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

type Record struct {
  Id        uint64
  Name      string
  Age       int
  CreatedAt time.Time
}

func TestCreateRecord(tt *testing.T) {
  t := NewT(tt, ContextConfig{
    MaxErrors: 3, // in case of failure, will dump up to 3 errors
  })

  before := time.Now()
  record, err := CreateRecord()

  if t.CmpNoError(err) {
    t.Log("No error, can now check struct contents")

    ok := t.Struct(record,
      &Record{
        Name: "Bob",
        Age:  23,
      },
      td.StructFields{
        "Id":        td.NotZero(),
        "CreatedAt": td.Between(before, time.Now()),
      },
      "Newly created record")
    if ok {
      t.Log(Record created successfully!")
    }
  }
}

"config" is an optional argument and, if passed, must be unique. It allows to configure how failures will be rendered during the lifetime of the returned instance.

t := NewT(tt)
t.Cmp(
  Record{Age: 12, Name: "Bob", Id: 12},  // got
  Record{Age: 21, Name: "John", Id: 28}) // expected

will produce:

=== RUN   TestFoobar
--- FAIL: TestFoobar (0.00s)
        foobar_test.go:88: Failed test
                DATA.Id: values differ
                             got: (uint64) 12
                        expected: (uint64) 28
                DATA.Name: values differ
                             got: "Bob"
                        expected: "John"
                DATA.Age: values differ
                             got: 12
                        expected: 28
FAIL

Now with a special configuration:

t := NewT(tt, ContextConfig{
    RootName:  "RECORD", // got data named "RECORD" instead of "DATA"
    MaxErrors: 2,        // stops after 2 errors instead of default 10
  })
t.Cmp(
  Record{Age: 12, Name: "Bob", Id: 12},  // got
  Record{Age: 21, Name: "John", Id: 28}, // expected
)

will produce:

=== RUN   TestFoobar
--- FAIL: TestFoobar (0.00s)
        foobar_test.go:96: Failed test
                RECORD.Id: values differ
                             got: (uint64) 12
                        expected: (uint64) 28
                RECORD.Name: values differ
                             got: "Bob"
                        expected: "John"
                Too many errors (use TESTDEEP_MAX_ERRORS=-1 to see all)
FAIL

See RootName method to configure RootName in a more specific fashion.

Note that setting MaxErrors to a negative value produces a dump with all errors.

If MaxErrors is not set (or set to 0), it is set to DefaultContextConfig.MaxErrors which is potentially dependent from the TESTDEEP_MAX_ERRORS environment variable (else defaults to 10.) See ContextConfig documentation for details.

Of course "t" can already be a *T, in this special case if "config" is omitted, the Config of the new instance is a copy of the "t" Config.

func Require

func Require(t testing.TB, config ...ContextConfig) *T

Require return a new T instance with FailureIsFatal flag set to true.

require := Require(t)

is roughly equivalent to:

require := NewT(t).FailureIsFatal(true)

See NewT documentation for usefulness of "config" optional parameter.

func (*T) A

func (t *T) A(operator TestDeep, model ...interface{}) interface{}

A is a synonym for Anchor.

import (
  "testing"

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

func TestFunc(tt *testing.T) {
  got := Func()

  t := td.NewT(tt)
  t.Cmp(got, &MyStruct{
    Name:    "Bob",
    Details: &MyDetails{
      Nick: t.A(td.HasPrefix("Bobby"), "").(string),
      Age:  t.A(td.Between(40, 50)).(int),
    },
  })
}

func (*T) All

func (t *T) All(got interface{}, expectedValues []interface{}, args ...interface{}) bool

All is a shortcut for:

t.Cmp(got, td.All(expectedValues...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#All for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "foo/bar"

ok := t.All(got, []interface{}{td.Re("o/b"), td.HasSuffix("bar"), "foo/bar"},
	"checks value %s", got)
fmt.Println(ok)

ok = t.All(got, []interface{}{td.Re("o/b"), td.HasSuffix("bar"), "fooX/Ybar"},
	"checks value %s", got)
fmt.Println(ok)

regOps := td.Flatten([]td.TestDeep{td.Re("o/b"), td.Re(`^fo`), td.Re(`ar$`)})
ok = t.All(got, []interface{}{td.HasPrefix("foo"), regOps, td.HasSuffix("bar")},
	"checks all operators against value %s", got)
fmt.Println(ok)
true
false
true

func (*T) Anchor

func (t *T) Anchor(operator TestDeep, model ...interface{}) interface{}

Anchor returns a typed value allowing to anchor the TestDeep operator "operator" in a go classic litteral like a struct, slice, array or map value.

If the TypeBehind method of "operator" returns non-nil, "model" can be omitted (like with Between operator in the example below). Otherwise, "model" should contain only one value corresponding to the returning type. It can be:

- a go value: returning type is the type of the value, whatever the value is
- a reflect.Type

It returns a typed value ready to be embed in a go data structure to be compared using T.Cmp or T.CmpLax:

import (
  "testing"

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

func TestFunc(tt *testing.T) {
  got := Func()

  t := td.NewT(tt)
  t.Cmp(got, &MyStruct{
    Name:    "Bob",
    Details: &MyDetails{
      Nick: t.Anchor(td.HasPrefix("Bobby"), "").(string),
      Age:  t.Anchor(td.Between(40, 50)).(int),
    },
  })
}

In this example:

HasPrefix operates on several input types (string, fmt.Stringer, error, …), so its TypeBehind method returns always nil as it can not guess in advance on which type it operates. In this case, we must pass "" as "model" parameter in order to tell it to return the string type. Note that the .(string) type assertion is then mandatory to conform to the strict type checking.

Between, on its side, knows the type on which it operates, as it is the same as the one of its parameters. So its TypeBehind method returns the right type, and so no need to pass it as "model" parameter. Note that the .(int) type assertion is still mandatory to conform to the strict type checking.

Without operator anchoring feature, the previous example would have been:

import (
  "testing"

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

func TestFunc(tt *testing.T) {
  got := Func()

  t := td.NewT(tt)
  t.Cmp(got, td.Struct(&MyStruct{Name: "Bob"},
    td.StructFields{
    "Details": td.Struct(&MyDetails{},
      td.StructFields{
        "Nick": td.HasPrefix("Bobby"),
        "Age":  td.Between(40, 50),
      }),
  }))
}

using two times the Struct operator to work around the strict type checking of golang.

By default, the value returned by Anchor can only be used in the next T.Cmp or T.CmpLax call. To make it persistent across calls, see SetAnchorsPersist and AnchorsPersistTemporarily methods.

See A method for a shorter synonym of Anchor.

func (*T) AnchorsPersistTemporarily

func (t *T) AnchorsPersistTemporarily() func()

AnchorsPersistTemporarily is used by helpers to temporarily enable anchors persistence. See tdhttp package for an example of use. It returns a function to be deferred, to restore the normal behavior (clear anchored operators if persistence was false, do nothing otherwise).

Typically used as:

defer t.AnchorsPersistTemporarily()()

func (*T) Any

func (t *T) Any(got interface{}, expectedValues []interface{}, args ...interface{}) bool

Any is a shortcut for:

t.Cmp(got, td.Any(expectedValues...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Any for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "foo/bar"

ok := t.Any(got, []interface{}{td.Re("zip"), td.HasSuffix("bar")},
	"checks value %s", got)
fmt.Println(ok)

ok = t.Any(got, []interface{}{td.Re("zip"), td.HasSuffix("foo")},
	"checks value %s", got)
fmt.Println(ok)

regOps := td.Flatten([]td.TestDeep{td.Re("a/c"), td.Re(`^xx`), td.Re(`ar$`)})
ok = t.Any(got, []interface{}{td.HasPrefix("xxx"), regOps, td.HasSuffix("zip")},
	"check at least one operator matches value %s", got)
fmt.Println(ok)
true
false
true

func (*T) Array

func (t *T) Array(got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool

Array is a shortcut for:

t.Cmp(got, td.Array(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Array for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Array)

Code:

t := td.NewT(&testing.T{})

got := [3]int{42, 58, 26}

ok := t.Array(got, [3]int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks array %v", got)
fmt.Println(ok)
true
Example (TypedArray)

Code:

t := td.NewT(&testing.T{})

type MyArray [3]int

got := MyArray{42, 58, 26}

ok := t.Array(got, MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks typed array %v", got)
fmt.Println(ok)

ok = t.Array(&got, &MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks pointer on typed array %v", got)
fmt.Println(ok)

ok = t.Array(&got, &MyArray{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed array %v", got)
fmt.Println(ok)

ok = t.Array(&got, (*MyArray)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed array %v", got)
fmt.Println(ok)
true
true
true
true

func (*T) ArrayEach

func (t *T) ArrayEach(got interface{}, expectedValue interface{}, args ...interface{}) bool

ArrayEach is a shortcut for:

t.Cmp(got, td.ArrayEach(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ArrayEach for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Array)

Code:

t := td.NewT(&testing.T{})

got := [3]int{42, 58, 26}

ok := t.ArrayEach(got, td.Between(25, 60),
	"checks each item of array %v is in [25 .. 60]", got)
fmt.Println(ok)
true
Example (Slice)

Code:

t := td.NewT(&testing.T{})

got := []int{42, 58, 26}

ok := t.ArrayEach(got, td.Between(25, 60),
	"checks each item of slice %v is in [25 .. 60]", got)
fmt.Println(ok)
true
Example (TypedArray)

Code:

t := td.NewT(&testing.T{})

type MyArray [3]int

got := MyArray{42, 58, 26}

ok := t.ArrayEach(got, td.Between(25, 60),
	"checks each item of typed array %v is in [25 .. 60]", got)
fmt.Println(ok)

ok = t.ArrayEach(&got, td.Between(25, 60),
	"checks each item of typed array pointer %v is in [25 .. 60]", got)
fmt.Println(ok)
true
true
Example (TypedSlice)

Code:

t := td.NewT(&testing.T{})

type MySlice []int

got := MySlice{42, 58, 26}

ok := t.ArrayEach(got, td.Between(25, 60),
	"checks each item of typed slice %v is in [25 .. 60]", got)
fmt.Println(ok)

ok = t.ArrayEach(&got, td.Between(25, 60),
	"checks each item of typed slice pointer %v is in [25 .. 60]", got)
fmt.Println(ok)
true
true

func (*T) Bag

func (t *T) Bag(got interface{}, expectedItems []interface{}, args ...interface{}) bool

Bag is a shortcut for:

t.Cmp(got, td.Bag(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Bag for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := t.Bag(got, []interface{}{1, 1, 2, 3, 5, 8, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = t.Bag(got, []interface{}{1, 2, 3, 5, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

got = []int{1, 3, 5, 8, 2}

ok = t.Bag(got, []interface{}{1, 1, 2, 3, 5, 8, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = t.Bag(got, []interface{}{1, 2, 3, 5, td.Gt(7)},
	"checks all items are present, in any order")
fmt.Println(ok)

expected := []int{1, 2, 3, 5}
ok = t.Bag(got, []interface{}{td.Flatten(expected), td.Gt(7)},
	"checks all expected items are present, in any order")
fmt.Println(ok)
true
false
false
true
true

func (*T) BeLax

func (t *T) BeLax(enable ...bool) *T

BeLax allows to to compare different but convertible types. If set to false, got and expected types must be the same. If set to true and expected type is convertible to got one, expected is first converted to go type before its comparison. See CmpLax function/method and Lax operator to set this flag without providing a specific configuration.

It returns a new instance of *T so does not alter the original t.

Note that t.BeLax() acts as t.BeLax(true).

func (*T) Between

func (t *T) Between(got interface{}, from interface{}, to interface{}, bounds BoundsKind, args ...interface{}) bool

Between is a shortcut for:

t.Cmp(got, td.Between(from, to, bounds), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Between for details.

Between() optional parameter "bounds" is here mandatory. td.BoundsInIn value should be passed to mimic its absence in original Between() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := td.NewT(&testing.T{})

got := 156

ok := t.Between(got, 154, 156, td.BoundsInIn,
	"checks %v is in [154 .. 156]", got)
fmt.Println(ok)

ok = t.Between(got, 154, 156, td.BoundsInIn,
	"checks %v is in [154 .. 156]", got)
fmt.Println(ok)

ok = t.Between(got, 154, 156, td.BoundsInOut,
	"checks %v is in [154 .. 156[", got)
fmt.Println(ok)

ok = t.Between(got, 154, 156, td.BoundsOutIn,
	"checks %v is in ]154 .. 156]", got)
fmt.Println(ok)

ok = t.Between(got, 154, 156, td.BoundsOutOut,
	"checks %v is in ]154 .. 156[", got)
fmt.Println(ok)
true
true
false
true
false
Example (String)

Code:

t := td.NewT(&testing.T{})

got := "abc"

ok := t.Between(got, "aaa", "abc", td.BoundsInIn,
	`checks "%v" is in ["aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = t.Between(got, "aaa", "abc", td.BoundsInIn,
	`checks "%v" is in ["aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = t.Between(got, "aaa", "abc", td.BoundsInOut,
	`checks "%v" is in ["aaa" .. "abc"[`, got)
fmt.Println(ok)

ok = t.Between(got, "aaa", "abc", td.BoundsOutIn,
	`checks "%v" is in ]"aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = t.Between(got, "aaa", "abc", td.BoundsOutOut,
	`checks "%v" is in ]"aaa" .. "abc"[`, got)
fmt.Println(ok)
true
true
false
true
false

func (*T) Cap

func (t *T) Cap(got interface{}, expectedCap interface{}, args ...interface{}) bool

Cap is a shortcut for:

t.Cmp(got, td.Cap(expectedCap), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Cap for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := make([]int, 0, 12)

ok := t.Cap(got, 12, "checks %v capacity is 12", got)
fmt.Println(ok)

ok = t.Cap(got, 0, "checks %v capacity is 0", got)
fmt.Println(ok)

got = nil

ok = t.Cap(got, 0, "checks %v capacity is 0", got)
fmt.Println(ok)
true
false
true
Example (Operator)

Code:

t := td.NewT(&testing.T{})

got := make([]int, 0, 12)

ok := t.Cap(got, td.Between(10, 12),
	"checks %v capacity is in [10 .. 12]", got)
fmt.Println(ok)

ok = t.Cap(got, td.Gt(10),
	"checks %v capacity is in [10 .. 12]", got)
fmt.Println(ok)
true
true

func (*T) Cmp

func (t *T) Cmp(got, expected interface{}, args ...interface{}) bool

Cmp is mostly a shortcut for:

Cmp(t.TB, got, expected, args...)

with the exception that t.Config is used to configure the test Context.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

func (*T) CmpDeeply

func (t *T) CmpDeeply(got, expected interface{}, args ...interface{}) bool

CmpDeeply works the same as Cmp and is still available for compatibility purpose. Use shorter Cmp in new code.

func (*T) CmpError

func (t *T) CmpError(got error, args ...interface{}) bool

CmpError checks that "got" is non-nil error.

_, err := MyFunction(1, 2, 3)
t.CmpError(err, "MyFunction(1, 2, 3) should return an error")

CmpError and not Error to avoid collision with t.TB.Error method.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := fmt.Errorf("Error #%d", 42)
ok := t.CmpError(got, "An error occurred")
fmt.Println(ok)

got = nil
ok = t.CmpError(got, "An error occurred")
fmt.Println(ok)
true
false

func (*T) CmpLax

func (t *T) CmpLax(got interface{}, expectedValue interface{}, args ...interface{}) bool

CmpLax is a shortcut for:

t.Cmp(got, td.Lax(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Lax for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

gotInt64 := int64(1234)
gotInt32 := int32(1235)

type myInt uint16
gotMyInt := myInt(1236)

expected := td.Between(1230, 1240)

ok := t.CmpLax(gotInt64, expected)
fmt.Println("int64 got between ints [1230 .. 1240]:", ok)

ok = t.CmpLax(gotInt32, expected)
fmt.Println("int32 got between ints [1230 .. 1240]:", ok)

ok = t.CmpLax(gotMyInt, expected)
fmt.Println("myInt got between ints [1230 .. 1240]:", ok)
int64 got between ints [1230 .. 1240]: true
int32 got between ints [1230 .. 1240]: true
myInt got between ints [1230 .. 1240]: true

func (*T) CmpNoError

func (t *T) CmpNoError(got error, args ...interface{}) bool

CmpNoError checks that "got" is nil error.

value, err := MyFunction(1, 2, 3)
if t.CmpNoError(err) {
  // one can now check value...
}

CmpNoError and not NoError to be consistent with CmpError method.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := fmt.Errorf("Error #%d", 42)
ok := t.CmpNoError(got, "An error occurred")
fmt.Println(ok)

got = nil
ok = t.CmpNoError(got, "An error occurred")
fmt.Println(ok)
false
true

func (*T) CmpNotPanic

func (t *T) CmpNotPanic(fn func(), args ...interface{}) bool

CmpNotPanic calls "fn" and checks no panic() occurred. If a panic() occurred false is returned then the panic() parameter and the stack trace appear in the test report.

Note that calling panic(nil) in "fn" body is detected as a panic.

t.CmpNotPanic(func() {}) // succeeds as function does not panic

t.CmpNotPanic(func() { panic("I am panicking!") }) // fails
t.CmpNotPanic(func() { panic(nil) })               // fails too

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.CmpNotPanic(func() {}, nil)
fmt.Println("checks a panic DID NOT occur:", ok)

ok = t.CmpNotPanic(func() { panic("I am panicking!") },
	"Hope it does not panic!")
fmt.Println("still no panic?", ok)

ok = t.CmpNotPanic(func() { panic(nil) }, "Checks for panic(nil)")
fmt.Println("last no panic?", ok)
checks a panic DID NOT occur: true
still no panic? false
last no panic? false

func (*T) CmpPanic

func (t *T) CmpPanic(fn func(), expected interface{}, args ...interface{}) bool

CmpPanic calls "fn" and checks a panic() occurred with the "expectedPanic" parameter. It returns true only if both conditions are fulfilled.

Note that calling panic(nil) in "fn" body is detected as a panic (in this case "expectedPanic" has to be nil).

t.CmpPanic(func() { panic("I am panicking!") },
  "I am panicking!",
  "The function should panic with the right string")

t.CmpPanic(func() { panic("I am panicking!") },
  Contains("panicking!"),
  "The function should panic with a string containing `panicking!`")

t.CmpPanic(t, func() { panic(nil) }, nil, "Checks for panic(nil)")

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.CmpPanic(func() { panic("I am panicking!") }, "I am panicking!",
	"Checks for panic")
fmt.Println("checks exact panic() string:", ok)

ok = t.CmpPanic(
	func() { panic("I am panicking!") },
	td.Contains("panicking!"),
	"Checks for panic")
fmt.Println("checks panic() sub-string:", ok)

ok = t.CmpPanic(func() { panic(nil) }, nil, "Checks for panic(nil)")
fmt.Println("checks for panic(nil):", ok)

// As well as structured data panic
type PanicStruct struct {
	Error string
	Code  int
}

ok = t.CmpPanic(
	func() {
		panic(PanicStruct{Error: "Memory violation", Code: 11})
	},
	PanicStruct{
		Error: "Memory violation",
		Code:  11,
	})
fmt.Println("checks exact panic() struct:", ok)

ok = t.CmpPanic(
	func() {
		panic(PanicStruct{Error: "Memory violation", Code: 11})
	},
	td.Struct(PanicStruct{}, td.StructFields{
		"Code": td.Between(10, 20),
	}))
fmt.Println("checks panic() struct against TestDeep operators:", ok)

ok = t.CmpPanic(func() {}, nil)
fmt.Println("checks a panic occurred:", ok)
checks exact panic() string: true
checks panic() sub-string: true
checks for panic(nil): true
checks exact panic() struct: true
checks panic() struct against TestDeep operators: true
checks a panic occurred: false

func (*T) Code

func (t *T) Code(got interface{}, fn interface{}, args ...interface{}) bool

Code is a shortcut for:

t.Cmp(got, td.Code(fn), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Code for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "12"

ok := t.Code(got, func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 10 && n < 100
},
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)

ok = t.Code(got, func(num string) (bool, string) {
	n, err := strconv.Atoi(num)
	if err != nil {
		return false, "not a number"
	}
	if n > 10 && n < 100 {
		return true, ""
	}
	return false, "not in ]10 .. 100["
},
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)

ok = t.Code(got, func(num string) error {
	n, err := strconv.Atoi(num)
	if err != nil {
		return err
	}
	if n > 10 && n < 100 {
		return nil
	}
	return fmt.Errorf("%d not in ]10 .. 100[", n)
},
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)
true
true
true

func (*T) Contains

func (t *T) Contains(got interface{}, expectedValue interface{}, args ...interface{}) bool

Contains is a shortcut for:

t.Cmp(got, td.Contains(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Contains for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (ArraySlice)

Code:

t := td.NewT(&testing.T{})

ok := t.Contains([...]int{11, 22, 33, 44}, 22)
fmt.Println("array contains 22:", ok)

ok = t.Contains([...]int{11, 22, 33, 44}, td.Between(20, 25))
fmt.Println("array contains at least one item in [20 .. 25]:", ok)

ok = t.Contains([]int{11, 22, 33, 44}, 22)
fmt.Println("slice contains 22:", ok)

ok = t.Contains([]int{11, 22, 33, 44}, td.Between(20, 25))
fmt.Println("slice contains at least one item in [20 .. 25]:", ok)

ok = t.Contains([]int{11, 22, 33, 44}, []int{22, 33})
fmt.Println("slice contains the sub-slice [22, 33]:", ok)
array contains 22: true
array contains at least one item in [20 .. 25]: true
slice contains 22: true
slice contains at least one item in [20 .. 25]: true
slice contains the sub-slice [22, 33]: true
Example (Error)

Code:

t := td.NewT(&testing.T{})

got := errors.New("foobar")

ok := t.Contains(got, "oob", "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = t.Contains(got, 'b', "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = t.Contains(got, byte('a'), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = t.Contains(got, td.Between('n', 'p'), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Map)

Code:

t := td.NewT(&testing.T{})

ok := t.Contains(map[string]int{"foo": 11, "bar": 22, "zip": 33}, 22)
fmt.Println("map contains value 22:", ok)

ok = t.Contains(map[string]int{"foo": 11, "bar": 22, "zip": 33}, td.Between(20, 25))
fmt.Println("map contains at least one value in [20 .. 25]:", ok)
map contains value 22: true
map contains at least one value in [20 .. 25]: true
Example (Nil)

Code:

t := td.NewT(&testing.T{})

num := 123
got := [...]*int{&num, nil}

ok := t.Contains(got, nil)
fmt.Println("array contains untyped nil:", ok)

ok = t.Contains(got, (*int)(nil))
fmt.Println("array contains *int nil:", ok)

ok = t.Contains(got, td.Nil())
fmt.Println("array contains Nil():", ok)

ok = t.Contains(got, (*byte)(nil))
fmt.Println("array contains *byte nil:", ok)
array contains untyped nil: true
array contains *int nil: true
array contains Nil(): true
array contains *byte nil: false
Example (String)

Code:

t := td.NewT(&testing.T{})

got := "foobar"

ok := t.Contains(got, "oob", "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = t.Contains(got, []byte("oob"), "checks %s", got)
fmt.Println("contains `oob` []byte:", ok)

ok = t.Contains(got, 'b', "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = t.Contains(got, byte('a'), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = t.Contains(got, td.Between('n', 'p'), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains `oob` []byte: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Stringer)

Code:

t := td.NewT(&testing.T{})

got := bytes.NewBufferString("foobar")

ok := t.Contains(got, "oob", "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = t.Contains(got, 'b', "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = t.Contains(got, byte('a'), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = t.Contains(got, td.Between('n', 'p'), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true

func (*T) ContainsKey

func (t *T) ContainsKey(got interface{}, expectedValue interface{}, args ...interface{}) bool

ContainsKey is a shortcut for:

t.Cmp(got, td.ContainsKey(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ContainsKey for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.ContainsKey(map[string]int{"foo": 11, "bar": 22, "zip": 33}, "foo")
fmt.Println(`map contains key "foo":`, ok)

ok = t.ContainsKey(map[int]bool{12: true, 24: false, 42: true, 51: false}, td.Between(40, 50))
fmt.Println("map contains at least a key in [40 .. 50]:", ok)

ok = t.ContainsKey(map[string]int{"FOO": 11, "bar": 22, "zip": 33}, td.Smuggle(strings.ToLower, "foo"))
fmt.Println(`map contains key "foo" without taking case into account:`, ok)
map contains key "foo": true
map contains at least a key in [40 .. 50]: true
map contains key "foo" without taking case into account: true
Example (Nil)

Code:

t := td.NewT(&testing.T{})

num := 1234
got := map[*int]bool{&num: false, nil: true}

ok := t.ContainsKey(got, nil)
fmt.Println("map contains untyped nil key:", ok)

ok = t.ContainsKey(got, (*int)(nil))
fmt.Println("map contains *int nil key:", ok)

ok = t.ContainsKey(got, td.Nil())
fmt.Println("map contains Nil() key:", ok)

ok = t.ContainsKey(got, (*byte)(nil))
fmt.Println("map contains *byte nil key:", ok)
map contains untyped nil key: true
map contains *int nil key: true
map contains Nil() key: true
map contains *byte nil key: false

func (*T) DoAnchorsPersist

func (t *T) DoAnchorsPersist() bool

DoAnchorsPersist returns true if anchors persistence is enabled, false otherwise.

func (*T) Empty

func (t *T) Empty(got interface{}, args ...interface{}) bool

Empty is a shortcut for:

t.Cmp(got, td.Empty(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Empty for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.Empty(nil)
fmt.Println(ok)

ok = t.Empty((*int)(nil))
fmt.Println(ok)

ok = t.Empty("")
fmt.Println(ok)

ok = t.Empty(0)
fmt.Println(ok)

ok = t.Empty((map[string]int)(nil))
fmt.Println(ok)

ok = t.Empty(map[string]int{})
fmt.Println(ok)

ok = t.Empty(([]int)(nil))
fmt.Println(ok)

ok = t.Empty([]int{})
fmt.Println(ok)

ok = t.Empty([]int{3})
fmt.Println(ok)

ok = t.Empty([3]int{})
fmt.Println(ok)
true
false
true
false
true
true
true
true
false
false
Example (Pointers)

Code:

t := td.NewT(&testing.T{})

type MySlice []int

ok := t.Empty(MySlice{})
fmt.Println(ok)

ok = t.Empty(&MySlice{})
fmt.Println(ok)

l1 := &MySlice{}
l2 := &l1
l3 := &l2
ok = t.Empty(&l3)
fmt.Println(ok)

// But not for others types as:
type MyStruct struct {
	Value int
}

ok = t.Empty(&MyStruct{})
fmt.Println(ok)
true
true
true
false

func (*T) FailureIsFatal

func (t *T) FailureIsFatal(enable ...bool) *T

FailureIsFatal allows to choose whether t.TB.Fatal() or t.TB.Error() will be used to print the next failure reports. When "enable" is true (or missing) testing.Fatal() will be called, else testing.Error(). Using *testing.T or *testing.B instance as t.TB value, FailNow() is called behind the scenes when Fatal() is called. See testing documentation for details.

It returns a new instance of *T so does not alter the original t and used as follows:

// Following t.Cmp() will call Fatal() if failure
t = t.FailureIsFatal()
t.Cmp(...)
t.Cmp(...)
// Following t.Cmp() won't call Fatal() if failure
t = t.FailureIsFatal(false)
t.Cmp(...)

or, if only one call is critic:

// This Cmp() call will call Fatal() if failure
t.FailureIsFatal().Cmp(...)
// Following t.Cmp() won't call Fatal() if failure
t.Cmp(...)
t.Cmp(...)

Note that t.FailureIsFatal() acts as t.FailureIsFatal(true).

func (*T) False

func (t *T) False(got interface{}, args ...interface{}) bool

False is shortcut for:

t.Cmp(got, false, args...)

Returns true if the test is OK, false if it fails.

t.False(IsAvailable(x), "x should not be available")

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := false
ok := t.False(got, "check that got is false!")
fmt.Println(ok)

got = true
ok = t.False(got, "check that got is false!")
fmt.Println(ok)
true
false

func (*T) Gt

func (t *T) Gt(got interface{}, minExpectedValue interface{}, args ...interface{}) bool

Gt is a shortcut for:

t.Cmp(got, td.Gt(minExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Gt for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := td.NewT(&testing.T{})

got := 156

ok := t.Gt(got, 155, "checks %v is > 155", got)
fmt.Println(ok)

ok = t.Gt(got, 156, "checks %v is > 156", got)
fmt.Println(ok)
true
false
Example (String)

Code:

t := td.NewT(&testing.T{})

got := "abc"

ok := t.Gt(got, "abb", `checks "%v" is > "abb"`, got)
fmt.Println(ok)

ok = t.Gt(got, "abc", `checks "%v" is > "abc"`, got)
fmt.Println(ok)
true
false

func (*T) Gte

func (t *T) Gte(got interface{}, minExpectedValue interface{}, args ...interface{}) bool

Gte is a shortcut for:

t.Cmp(got, td.Gte(minExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Gte for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := td.NewT(&testing.T{})

got := 156

ok := t.Gte(got, 156, "checks %v is ≥ 156", got)
fmt.Println(ok)

ok = t.Gte(got, 155, "checks %v is ≥ 155", got)
fmt.Println(ok)

ok = t.Gte(got, 157, "checks %v is ≥ 157", got)
fmt.Println(ok)
true
true
false
Example (String)

Code:

t := td.NewT(&testing.T{})

got := "abc"

ok := t.Gte(got, "abc", `checks "%v" is ≥ "abc"`, got)
fmt.Println(ok)

ok = t.Gte(got, "abb", `checks "%v" is ≥ "abb"`, got)
fmt.Println(ok)

ok = t.Gte(got, "abd", `checks "%v" is ≥ "abd"`, got)
fmt.Println(ok)
true
true
false

func (*T) HasPrefix

func (t *T) HasPrefix(got interface{}, expected string, args ...interface{}) bool

HasPrefix is a shortcut for:

t.Cmp(got, td.HasPrefix(expected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#HasPrefix for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "foobar"

ok := t.HasPrefix(got, "foo", "checks %s", got)
fmt.Println("using string:", ok)

ok = t.Cmp([]byte(got), td.HasPrefix("foo"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := td.NewT(&testing.T{})

got := errors.New("foobar")

ok := t.HasPrefix(got, "foo", "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := td.NewT(&testing.T{})

got := bytes.NewBufferString("foobar")

ok := t.HasPrefix(got, "foo", "checks %s", got)
fmt.Println(ok)
true

func (*T) HasSuffix

func (t *T) HasSuffix(got interface{}, expected string, args ...interface{}) bool

HasSuffix is a shortcut for:

t.Cmp(got, td.HasSuffix(expected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#HasSuffix for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "foobar"

ok := t.HasSuffix(got, "bar", "checks %s", got)
fmt.Println("using string:", ok)

ok = t.Cmp([]byte(got), td.HasSuffix("bar"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := td.NewT(&testing.T{})

got := errors.New("foobar")

ok := t.HasSuffix(got, "bar", "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := td.NewT(&testing.T{})

got := bytes.NewBufferString("foobar")

ok := t.HasSuffix(got, "bar", "checks %s", got)
fmt.Println(ok)
true

func (*T) Isa

func (t *T) Isa(got interface{}, model interface{}, args ...interface{}) bool

Isa is a shortcut for:

t.Cmp(got, td.Isa(model), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Isa for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

type TstStruct struct {
	Field int
}

got := TstStruct{Field: 1}

ok := t.Isa(got, TstStruct{}, "checks got is a TstStruct")
fmt.Println(ok)

ok = t.Isa(got, &TstStruct{},
	"checks got is a pointer on a TstStruct")
fmt.Println(ok)

ok = t.Isa(&got, &TstStruct{},
	"checks &got is a pointer on a TstStruct")
fmt.Println(ok)
true
false
true
Example (Interface)

Code:

t := td.NewT(&testing.T{})

got := bytes.NewBufferString("foobar")

ok := t.Isa(got, (*fmt.Stringer)(nil),
	"checks got implements fmt.Stringer interface")
fmt.Println(ok)

errGot := fmt.Errorf("An error #%d occurred", 123)

ok = t.Isa(errGot, (*error)(nil),
	"checks errGot is a *error or implements error interface")
fmt.Println(ok)

errGot = nil

ok = t.Isa(errGot, (*error)(nil),
	"checks errGot is a *error or implements error interface")
fmt.Println(ok)

ok = t.Isa(&errGot, (*error)(nil),
	"checks &errGot is a *error or implements error interface")
fmt.Println(ok)
true
true
false
true

func (*T) JSON

func (t *T) JSON(got interface{}, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

JSON is a shortcut for:

t.Cmp(got, td.JSON(expectedJSON, params...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#JSON for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Basic)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

ok := t.JSON(got, `{"age":42,"fullname":"Bob"}`, nil)
fmt.Println("check got with age then fullname:", ok)

ok = t.JSON(got, `{"fullname":"Bob","age":42}`, nil)
fmt.Println("check got with fullname then age:", ok)

ok = t.JSON(got, `
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42     /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
}`, nil)
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = t.JSON(got, `{"fullname":"Bob","age":42,"gender":"male"}`, nil)
fmt.Println("check got with gender field:", ok)

ok = t.JSON(got, `{"fullname":"Bob"}`, nil)
fmt.Println("check got with fullname only:", ok)

ok = t.JSON(true, `true`, nil)
fmt.Println("check boolean got is true:", ok)

ok = t.JSON(42, `42`, nil)
fmt.Println("check numeric got is 42:", ok)

got = nil
ok = t.JSON(got, `null`, nil)
fmt.Println("check nil got is null:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got with gender field: false
check got with fullname only: false
check boolean got is true: true
check numeric got is 42: true
check nil got is null: true
Example (File)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender"
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := t.JSON(got, filename, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = t.JSON(got, file, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
}

ok := t.JSON(got, `{"age": $1, "fullname": $2}`, []interface{}{42, "Bob Foobar"})
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = t.JSON(got, `{"age": $1, "fullname": $2}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar")})
fmt.Println("check got with numeric placeholders:", ok)

ok = t.JSON(got, `{"age": "$1", "fullname": "$2"}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar")})
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = t.JSON(got, `{"age": $age, "fullname": $name}`, []interface{}{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar"))})
fmt.Println("check got with named placeholders:", ok)

ok = t.JSON(got, `{"age": $^NotZero, "fullname": $^NotEmpty}`, nil)
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func (*T) Keys

func (t *T) Keys(got interface{}, val interface{}, args ...interface{}) bool

Keys is a shortcut for:

t.Cmp(got, td.Keys(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Keys for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

ok := t.Keys(got, []string{"bar", "foo", "zip"})
fmt.Println("All sorted keys are found:", ok)

ok = t.Keys(got, []string{"zip", "bar", "foo"})
fmt.Println("All unsorted keys are found:", ok)

ok = t.Keys(got, td.Bag("zip", "bar", "foo"))
fmt.Println("All unsorted keys are found, with the help of Bag operator:", ok)

ok = t.Keys(got, td.ArrayEach(td.Len(3)))
fmt.Println("Each key is 3 bytes long:", ok)
All sorted keys are found: true
All unsorted keys are found: false
All unsorted keys are found, with the help of Bag operator: true
Each key is 3 bytes long: true

func (*T) Len

func (t *T) Len(got interface{}, expectedLen interface{}, args ...interface{}) bool

Len is a shortcut for:

t.Cmp(got, td.Len(expectedLen), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Len for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := td.NewT(&testing.T{})

got := map[int]bool{11: true, 22: false, 33: false}

ok := t.Len(got, 3, "checks %v len is 3", got)
fmt.Println(ok)

ok = t.Len(got, 0, "checks %v len is 0", got)
fmt.Println(ok)

got = nil

ok = t.Len(got, 0, "checks %v len is 0", got)
fmt.Println(ok)
true
false
true
Example (OperatorMap)

Code:

t := td.NewT(&testing.T{})

got := map[int]bool{11: true, 22: false, 33: false}

ok := t.Len(got, td.Between(3, 8),
	"checks %v len is in [3 .. 8]", got)
fmt.Println(ok)

ok = t.Len(got, td.Gte(3), "checks %v len is ≥ 3", got)
fmt.Println(ok)
true
true
Example (OperatorSlice)

Code:

t := td.NewT(&testing.T{})

got := []int{11, 22, 33}

ok := t.Len(got, td.Between(3, 8),
	"checks %v len is in [3 .. 8]", got)
fmt.Println(ok)

ok = t.Len(got, td.Lt(5), "checks %v len is < 5", got)
fmt.Println(ok)
true
true
Example (Slice)

Code:

t := td.NewT(&testing.T{})

got := []int{11, 22, 33}

ok := t.Len(got, 3, "checks %v len is 3", got)
fmt.Println(ok)

ok = t.Len(got, 0, "checks %v len is 0", got)
fmt.Println(ok)

got = nil

ok = t.Len(got, 0, "checks %v len is 0", got)
fmt.Println(ok)
true
false
true

func (*T) Lt

func (t *T) Lt(got interface{}, maxExpectedValue interface{}, args ...interface{}) bool

Lt is a shortcut for:

t.Cmp(got, td.Lt(maxExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Lt for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := td.NewT(&testing.T{})

got := 156

ok := t.Lt(got, 157, "checks %v is < 157", got)
fmt.Println(ok)

ok = t.Lt(got, 156, "checks %v is < 156", got)
fmt.Println(ok)
true
false
Example (String)

Code:

t := td.NewT(&testing.T{})

got := "abc"

ok := t.Lt(got, "abd", `checks "%v" is < "abd"`, got)
fmt.Println(ok)

ok = t.Lt(got, "abc", `checks "%v" is < "abc"`, got)
fmt.Println(ok)
true
false

func (*T) Lte

func (t *T) Lte(got interface{}, maxExpectedValue interface{}, args ...interface{}) bool

Lte is a shortcut for:

t.Cmp(got, td.Lte(maxExpectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Lte for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Int)

Code:

t := td.NewT(&testing.T{})

got := 156

ok := t.Lte(got, 156, "checks %v is ≤ 156", got)
fmt.Println(ok)

ok = t.Lte(got, 157, "checks %v is ≤ 157", got)
fmt.Println(ok)

ok = t.Lte(got, 155, "checks %v is ≤ 155", got)
fmt.Println(ok)
true
true
false
Example (String)

Code:

t := td.NewT(&testing.T{})

got := "abc"

ok := t.Lte(got, "abc", `checks "%v" is ≤ "abc"`, got)
fmt.Println(ok)

ok = t.Lte(got, "abd", `checks "%v" is ≤ "abd"`, got)
fmt.Println(ok)

ok = t.Lte(got, "abb", `checks "%v" is ≤ "abb"`, got)
fmt.Println(ok)
true
true
false

func (*T) Map

func (t *T) Map(got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool

Map is a shortcut for:

t.Cmp(got, td.Map(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Map for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := td.NewT(&testing.T{})

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := t.Map(got, map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()},
	"checks map %v", got)
fmt.Println(ok)

ok = t.Map(got, map[string]int{}, td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks map %v", got)
fmt.Println(ok)

ok = t.Map(got, (map[string]int)(nil), td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks map %v", got)
fmt.Println(ok)
true
true
true
Example (TypedMap)

Code:

t := td.NewT(&testing.T{})

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := t.Map(got, MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()},
	"checks typed map %v", got)
fmt.Println(ok)

ok = t.Map(&got, &MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()},
	"checks pointer on typed map %v", got)
fmt.Println(ok)

ok = t.Map(&got, &MyMap{}, td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks pointer on typed map %v", got)
fmt.Println(ok)

ok = t.Map(&got, (*MyMap)(nil), td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()},
	"checks pointer on typed map %v", got)
fmt.Println(ok)
true
true
true
true

func (*T) MapEach

func (t *T) MapEach(got interface{}, expectedValue interface{}, args ...interface{}) bool

MapEach is a shortcut for:

t.Cmp(got, td.MapEach(expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#MapEach for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := td.NewT(&testing.T{})

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := t.MapEach(got, td.Between(10, 90),
	"checks each value of map %v is in [10 .. 90]", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := td.NewT(&testing.T{})

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := t.MapEach(got, td.Between(10, 90),
	"checks each value of typed map %v is in [10 .. 90]", got)
fmt.Println(ok)

ok = t.MapEach(&got, td.Between(10, 90),
	"checks each value of typed map pointer %v is in [10 .. 90]", got)
fmt.Println(ok)
true
true

func (*T) N

func (t *T) N(got interface{}, num interface{}, tolerance interface{}, args ...interface{}) bool

N is a shortcut for:

t.Cmp(got, td.N(num, tolerance), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#N for details.

N() optional parameter "tolerance" is here mandatory. 0 value should be passed to mimic its absence in original N() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := 1.12345

ok := t.N(got, 1.1234, 0.00006,
	"checks %v = 1.1234 ± 0.00006", got)
fmt.Println(ok)
true

func (*T) NaN

func (t *T) NaN(got interface{}, args ...interface{}) bool

NaN is a shortcut for:

t.Cmp(got, td.NaN(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NaN for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Float32)

Code:

t := td.NewT(&testing.T{})

got := float32(math.NaN())

ok := t.NaN(got,
	"checks %v is not-a-number", got)

fmt.Println("float32(math.NaN()) is float32 not-a-number:", ok)

got = 12

ok = t.NaN(got,
	"checks %v is not-a-number", got)

fmt.Println("float32(12) is float32 not-a-number:", ok)
float32(math.NaN()) is float32 not-a-number: true
float32(12) is float32 not-a-number: false
Example (Float64)

Code:

t := td.NewT(&testing.T{})

got := math.NaN()

ok := t.NaN(got,
	"checks %v is not-a-number", got)

fmt.Println("math.NaN() is not-a-number:", ok)

got = 12

ok = t.NaN(got,
	"checks %v is not-a-number", got)

fmt.Println("float64(12) is not-a-number:", ok)

func (*T) Nil

func (t *T) Nil(got interface{}, args ...interface{}) bool

Nil is a shortcut for:

t.Cmp(got, td.Nil(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Nil for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

var got fmt.Stringer // interface

ok := t.Cmp(got, nil)
fmt.Println(ok)

ok = t.Nil(got)
fmt.Println(ok)

got = (*bytes.Buffer)(nil)

ok = t.Cmp(got, nil)
fmt.Println(ok)

ok = t.Nil(got)
fmt.Println(ok)
true
true
false
true

func (*T) None

func (t *T) None(got interface{}, notExpectedValues []interface{}, args ...interface{}) bool

None is a shortcut for:

t.Cmp(got, td.None(notExpectedValues...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#None for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := 18

ok := t.None(got, []interface{}{0, 10, 20, 30, td.Between(100, 199)},
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

got = 20

ok = t.None(got, []interface{}{0, 10, 20, 30, td.Between(100, 199)},
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

got = 142

ok = t.None(got, []interface{}{0, 10, 20, 30, td.Between(100, 199)},
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

prime := td.Flatten([]int{1, 2, 3, 5, 7, 11, 13})
even := td.Flatten([]int{2, 4, 6, 8, 10, 12, 14})
for _, got := range [...]int{9, 3, 8, 15} {
	ok = t.None(got, []interface{}{prime, even, td.Gt(14)},
		"checks %v is not prime number, nor an even number and not > 14")
	fmt.Printf("%d → %t\n", got, ok)
}
true
false
false
9 → true
3 → false
8 → false
15 → false

func (*T) Not

func (t *T) Not(got interface{}, notExpected interface{}, args ...interface{}) bool

Not is a shortcut for:

t.Cmp(got, td.Not(notExpected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Not for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := 42

ok := t.Not(got, 0, "checks %v is non-null", got)
fmt.Println(ok)

ok = t.Not(got, td.Between(10, 30),
	"checks %v is not in [10 .. 30]", got)
fmt.Println(ok)

got = 0

ok = t.Not(got, 0, "checks %v is non-null", got)
fmt.Println(ok)
true
true
false

func (*T) NotAny

func (t *T) NotAny(got interface{}, notExpectedItems []interface{}, args ...interface{}) bool

NotAny is a shortcut for:

t.Cmp(got, td.NotAny(notExpectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotAny for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{4, 5, 9, 42}

ok := t.NotAny(got, []interface{}{3, 6, 8, 41, 43},
	"checks %v contains no item listed in NotAny()", got)
fmt.Println(ok)

ok = t.NotAny(got, []interface{}{3, 6, 8, 42, 43},
	"checks %v contains no item listed in NotAny()", got)
fmt.Println(ok)

notExpected := []int{3, 6, 8, 41, 43}
ok = t.NotAny(got, []interface{}{td.Flatten(notExpected)},
	"checks %v contains no item listed in notExpected", got)
fmt.Println(ok)
true
false
true

func (*T) NotEmpty

func (t *T) NotEmpty(got interface{}, args ...interface{}) bool

NotEmpty is a shortcut for:

t.Cmp(got, td.NotEmpty(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotEmpty for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.NotEmpty(nil)
fmt.Println(ok)

ok = t.NotEmpty("foobar")
fmt.Println(ok)

ok = t.NotEmpty(0)
fmt.Println(ok)

ok = t.NotEmpty(map[string]int{"foobar": 42})
fmt.Println(ok)

ok = t.NotEmpty([]int{1})
fmt.Println(ok)

ok = t.NotEmpty([3]int{})
fmt.Println(ok)
false
true
false
true
true
true
Example (Pointers)

Code:

t := td.NewT(&testing.T{})

type MySlice []int

ok := t.NotEmpty(MySlice{12})
fmt.Println(ok)

ok = t.NotEmpty(&MySlice{12})
fmt.Println(ok)

l1 := &MySlice{12}
l2 := &l1
l3 := &l2
ok = t.NotEmpty(&l3)
fmt.Println(ok)

// But not for others types as:
type MyStruct struct {
	Value int
}

ok = t.NotEmpty(&MyStruct{})
fmt.Println(ok)
true
true
true
false

func (*T) NotNaN

func (t *T) NotNaN(got interface{}, args ...interface{}) bool

NotNaN is a shortcut for:

t.Cmp(got, td.NotNaN(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotNaN for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Float32)

Code:

t := td.NewT(&testing.T{})

got := float32(math.NaN())

ok := t.NotNaN(got,
	"checks %v is not-a-number", got)

fmt.Println("float32(math.NaN()) is NOT float32 not-a-number:", ok)

got = 12

ok = t.NotNaN(got,
	"checks %v is not-a-number", got)

fmt.Println("float32(12) is NOT float32 not-a-number:", ok)
float32(math.NaN()) is NOT float32 not-a-number: false
float32(12) is NOT float32 not-a-number: true
Example (Float64)

Code:

t := td.NewT(&testing.T{})

got := math.NaN()

ok := t.NotNaN(got,
	"checks %v is not-a-number", got)

fmt.Println("math.NaN() is not-a-number:", ok)

got = 12

ok = t.NotNaN(got,
	"checks %v is not-a-number", got)

fmt.Println("float64(12) is not-a-number:", ok)

func (*T) NotNil

func (t *T) NotNil(got interface{}, args ...interface{}) bool

NotNil is a shortcut for:

t.Cmp(got, td.NotNil(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotNil for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

var got fmt.Stringer = &bytes.Buffer{}

ok := t.Cmp(got, td.Not(nil))
fmt.Println(ok)

ok = t.NotNil(got)
fmt.Println(ok)

got = (*bytes.Buffer)(nil)

ok = t.Cmp(got, td.Not(nil))
fmt.Println(ok)

ok = t.NotNil(got)
fmt.Println(ok)
true
true
true
false

func (*T) NotZero

func (t *T) NotZero(got interface{}, args ...interface{}) bool

NotZero is a shortcut for:

t.Cmp(got, td.NotZero(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#NotZero for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.NotZero(0)
fmt.Println(ok)

ok = t.NotZero(float64(0))
fmt.Println(ok)

ok = t.NotZero(12)
fmt.Println(ok)

ok = t.NotZero((map[string]int)(nil))
fmt.Println(ok)

ok = t.NotZero(map[string]int{})
fmt.Println(ok)

ok = t.NotZero(([]int)(nil))
fmt.Println(ok)

ok = t.NotZero([]int{})
fmt.Println(ok)

ok = t.NotZero([3]int{})
fmt.Println(ok)

ok = t.NotZero([3]int{0, 1})
fmt.Println(ok)

ok = t.NotZero(bytes.Buffer{})
fmt.Println(ok)

ok = t.NotZero(&bytes.Buffer{})
fmt.Println(ok)

ok = t.Cmp(&bytes.Buffer{}, td.Ptr(td.NotZero()))
fmt.Println(ok)
false
false
true
false
true
false
true
false
true
false
true
false

func (*T) PPtr

func (t *T) PPtr(got interface{}, val interface{}, args ...interface{}) bool

PPtr is a shortcut for:

t.Cmp(got, td.PPtr(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#PPtr for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

num := 12
got := &num

ok := t.PPtr(&got, 12)
fmt.Println(ok)

ok = t.PPtr(&got, td.Between(4, 15))
fmt.Println(ok)
true
true

func (*T) Ptr

func (t *T) Ptr(got interface{}, val interface{}, args ...interface{}) bool

Ptr is a shortcut for:

t.Cmp(got, td.Ptr(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Ptr for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := 12

ok := t.Ptr(&got, 12)
fmt.Println(ok)

ok = t.Ptr(&got, td.Between(4, 15))
fmt.Println(ok)
true
true

func (*T) Re

func (t *T) Re(got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool

Re is a shortcut for:

t.Cmp(got, td.Re(reg, capture), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Re for details.

Re() optional parameter "capture" is here mandatory. nil value should be passed to mimic its absence in original Re() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "foo bar"
ok := t.Re(got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)

got = "bar foo"
ok = t.Re(got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)
true
false
Example (Capture)

Code:

t := td.NewT(&testing.T{})

got := "foo bar biz"
ok := t.Re(got, `^(\w+) (\w+) (\w+)$`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo bar! biz"
ok = t.Re(got, `^(\w+) (\w+) (\w+)$`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (Compiled)

Code:

t := td.NewT(&testing.T{})

expected := regexp.MustCompile("(zip|bar)$")

got := "foo bar"
ok := t.Re(got, expected, nil, "checks value %s", got)
fmt.Println(ok)

got = "bar foo"
ok = t.Re(got, expected, nil, "checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCapture)

Code:

t := td.NewT(&testing.T{})

expected := regexp.MustCompile(`^(\w+) (\w+) (\w+)$`)

got := "foo bar biz"
ok := t.Re(got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo bar! biz"
ok = t.Re(got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledError)

Code:

t := td.NewT(&testing.T{})

expected := regexp.MustCompile("(zip|bar)$")

got := errors.New("foo bar")
ok := t.Re(got, expected, nil, "checks value %s", got)
fmt.Println(ok)
true
Example (CompiledStringer)

Code:

t := td.NewT(&testing.T{})

expected := regexp.MustCompile("(zip|bar)$")

got := bytes.NewBufferString("foo bar")
ok := t.Re(got, expected, nil, "checks value %s", got)
fmt.Println(ok)
true
Example (Error)

Code:

t := td.NewT(&testing.T{})

got := errors.New("foo bar")
ok := t.Re(got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := td.NewT(&testing.T{})

got := bytes.NewBufferString("foo bar")
ok := t.Re(got, "(zip|bar)$", nil, "checks value %s", got)
fmt.Println(ok)
true

func (*T) ReAll

func (t *T) ReAll(got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool

ReAll is a shortcut for:

t.Cmp(got, td.ReAll(reg, capture), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ReAll for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Capture)

Code:

t := td.NewT(&testing.T{})

got := "foo bar biz"
ok := t.ReAll(got, `(\w+)`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo BAR biz"
ok = t.ReAll(got, `(\w+)`, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CaptureComplex)

Code:

t := td.NewT(&testing.T{})

got := "11 45 23 56 85 96"
ok := t.ReAll(got, `(\d+)`, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 10 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)

ok = t.ReAll(got, `(\d+)`, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 20 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCapture)

Code:

t := td.NewT(&testing.T{})

expected := regexp.MustCompile(`(\w+)`)

got := "foo bar biz"
ok := t.ReAll(got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)

got = "foo BAR biz"
ok = t.ReAll(got, expected, td.Set("biz", "foo", "bar"),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCaptureComplex)

Code:

t := td.NewT(&testing.T{})

expected := regexp.MustCompile(`(\d+)`)

got := "11 45 23 56 85 96"
ok := t.ReAll(got, expected, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 10 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)

ok = t.ReAll(got, expected, td.ArrayEach(td.Code(func(num string) bool {
	n, err := strconv.Atoi(num)
	return err == nil && n > 20 && n < 100
})),
	"checks value %s", got)
fmt.Println(ok)
true
false

func (*T) ResetAnchors

func (t *T) ResetAnchors()

ResetAnchors frees all operators anchored with Anchor method. Unless operators anchoring persistence has been enabled with SetAnchorsPersist, there is no need to call this method. Anchored operators are automatically freed after each Cmp, CmpDeeply and CmpPanic call (or others methods calling them behind the scene).

func (*T) RootName

func (t *T) RootName(rootName string) *T

RootName changes the name of the got data. By default it is "DATA". For an HTTP response body, it could be "BODY" for example.

It returns a new instance of *T so does not alter the original t and used as follows:

t.RootName("RECORD").
  Struct(record,
    &Record{
      Name: "Bob",
      Age:  23,
    },
    td.StructFields{
      "Id":        td.NotZero(),
      "CreatedAt": td.Between(before, time.Now()),
    },
    "Newly created record")

In case of error for the field Age, the failure message will contain:

RECORD.Age: values differ

Which is more readable than the generic:

DATA.Age: values differ

If "" is passed the name is set to "DATA", the default value.

func (*T) Run

func (t *T) Run(name string, f func(t *T)) bool

Run runs "f" as a subtest of t called "name".

If t.TB implement a method with the following signature:

(X) Run(string, func(X)) bool

it calls it with a function of its own in which it creates a new instance of *T on the fly before calling "f" with it.

So if t.TB is a *testing.T or a *testing.B (which is in normal cases), let's quote the testing.T.Run() & testing.B.Run() documentation: "f" is called in a separate goroutine and blocks until "f" returns or calls t.Parallel to become a parallel test. Run reports whether "f" succeeded (or at least did not fail before calling t.Parallel). Run may be called simultaneously from multiple goroutines, but all such calls must return before the outer test function for t returns.

If this Run() method is not found, it simply logs "name" then executes "f" using a new *T instance in the current goroutine. Note that it is only done for convenience.

The "t" param of "f" inherits the configuration of the self-reference.

func (*T) RunAssertRequire

func (t *T) RunAssertRequire(name string, f func(assert *T, require *T)) bool

RunAssertRequire runs "f" as a subtest of t called "name".

If t.TB implement a method with the following signature:

(X) Run(string, func(X)) bool

it calls it with a function of its own in which it creates two new instances of *T using AssertRequire() on the fly before calling "f" with them.

So if t.TB is a *testing.T or a *testing.B (which is in normal cases), let's quote the testing.T.Run() & testing.B.Run() documentation: "f" is called in a separate goroutine and blocks until "f" returns or calls t.Parallel to become a parallel test. Run reports whether "f" succeeded (or at least did not fail before calling t.Parallel). Run may be called simultaneously from multiple goroutines, but all such calls must return before the outer test function for t returns.

If this Run() method is not found, it simply logs "name" then executes "f" using two new instances of *T (built with AssertRequire()) in the current goroutine. Note that it is only done for convenience.

The "assert" and "require" params of "f" inherit the configuration of the self-reference, except that a failure is never fatal using "assert" and always fatal using "require".

func (*T) RunT

func (t *T) RunT(name string, f func(t *T)) bool

RunT runs "f" as a subtest of t called "name".

Deprecated: RunT has been superseded by Run() method. It is kept for compatibility.

func (*T) SStruct

func (t *T) SStruct(got interface{}, model interface{}, expectedFields StructFields, args ...interface{}) bool

SStruct is a shortcut for:

t.Cmp(got, td.SStruct(model, expectedFields), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SStruct for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

type Person struct {
	Name        string
	Age         int
	NumChildren int
}

got := Person{
	Name:        "Foobar",
	Age:         42,
	NumChildren: 0,
}

ok := t.SStruct(got, Person{Name: "Foobar"}, td.StructFields{
	"Age": td.Between(40, 50),
},
	"checks %v is the right Person")
fmt.Println(ok)

got.NumChildren = 3
ok = t.SStruct(got, Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = t.SStruct(&got, &Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = t.SStruct(&got, (*Person)(nil), td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)
true
true
true
true

func (*T) Set

func (t *T) Set(got interface{}, expectedItems []interface{}, args ...interface{}) bool

Set is a shortcut for:

t.Cmp(got, td.Set(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Set for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := t.Set(got, []interface{}{1, 2, 3, 5, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = t.Set(got, []interface{}{1, 2, 2, 2, 2, 2, 3, 5, 8},
	"checks all items are present, in any order")
fmt.Println(ok)

ok = t.Set(got, []interface{}{td.Between(1, 4), 3, td.Between(2, 10)},
	"checks all items are present, in any order")
fmt.Println(ok)

expected := []int{1, 2, 3, 5, 8}
ok = t.Set(got, []interface{}{td.Flatten(expected)},
	"checks all expected items are present, in any order")
fmt.Println(ok)
true
true
true
true

func (*T) SetAnchorsPersist

func (t *T) SetAnchorsPersist(persist bool)

SetAnchorsPersist allows to enable or disable anchors persistence.

func (*T) Shallow

func (t *T) Shallow(got interface{}, expectedPtr interface{}, args ...interface{}) bool

Shallow is a shortcut for:

t.Cmp(got, td.Shallow(expectedPtr), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Shallow for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

type MyStruct struct {
	Value int
}
data := MyStruct{Value: 12}
got := &data

ok := t.Shallow(got, &data,
	"checks pointers only, not contents")
fmt.Println(ok)

ok = t.Shallow(got, &MyStruct{Value: 12},
	"checks pointers only, not contents")
fmt.Println(ok)
true
false
Example (Slice)

Code:

t := td.NewT(&testing.T{})

back := []int{1, 2, 3, 1, 2, 3}
a := back[:3]
b := back[3:]

ok := t.Shallow(a, back)
fmt.Println("are ≠ but share the same area:", ok)

ok = t.Shallow(b, back)
fmt.Println("are = but do not point to same area:", ok)
are ≠ but share the same area: true
are = but do not point to same area: false
Example (String)

Code:

t := td.NewT(&testing.T{})

back := "foobarfoobar"
a := back[:6]
b := back[6:]

ok := t.Shallow(a, back)
fmt.Println("are ≠ but share the same area:", ok)

ok = t.Shallow(b, a)
fmt.Println("are = but do not point to same area:", ok)
are ≠ but share the same area: true
are = but do not point to same area: false

func (*T) Slice

func (t *T) Slice(got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool

Slice is a shortcut for:

t.Cmp(got, td.Slice(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Slice for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Slice)

Code:

t := td.NewT(&testing.T{})

got := []int{42, 58, 26}

ok := t.Slice(got, []int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks slice %v", got)
fmt.Println(ok)

ok = t.Slice(got, []int{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks slice %v", got)
fmt.Println(ok)

ok = t.Slice(got, ([]int)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks slice %v", got)
fmt.Println(ok)
true
true
true
Example (TypedSlice)

Code:

t := td.NewT(&testing.T{})

type MySlice []int

got := MySlice{42, 58, 26}

ok := t.Slice(got, MySlice{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks typed slice %v", got)
fmt.Println(ok)

ok = t.Slice(&got, &MySlice{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
	"checks pointer on typed slice %v", got)
fmt.Println(ok)

ok = t.Slice(&got, &MySlice{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed slice %v", got)
fmt.Println(ok)

ok = t.Slice(&got, (*MySlice)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
	"checks pointer on typed slice %v", got)
fmt.Println(ok)
true
true
true
true

func (*T) Smuggle

func (t *T) Smuggle(got interface{}, fn interface{}, expectedValue interface{}, args ...interface{}) bool

Smuggle is a shortcut for:

t.Cmp(got, td.Smuggle(fn, expectedValue), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Smuggle for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Auto_unmarshal)

Code:

t := td.NewT(&testing.T{})

got := []byte(`{"a":1,"b":2}`)

ok := t.Smuggle(got, func(b json.RawMessage) (r map[string]int, err error) {
	err = json.Unmarshal(b, &r)
	return
}, map[string]int{
	"a": 1,
	"b": 2,
})
fmt.Println("JSON contents is OK:", ok)
JSON contents is OK: true
Example (Complex)

Code:

t := td.NewT(&testing.T{})

// No end date but a start date and a duration
type StartDuration struct {
	StartDate time.Time
	Duration  time.Duration
}

for _, duration := range []time.Duration{48, 72, 96} {
	got := StartDuration{
		StartDate: time.Date(2018, time.February, 14, 12, 13, 14, 0, time.UTC),
		Duration:  duration * time.Hour,
	}

	ok := t.Smuggle(got, func(sd StartDuration) time.Time {
		return sd.StartDate.Add(sd.Duration)
	}, td.Between(
		time.Date(2018, time.February, 17, 0, 0, 0, 0, time.UTC),
		time.Date(2018, time.February, 19, 0, 0, 0, 0, time.UTC)))
	fmt.Println(ok)

	ok = t.Smuggle(got, func(sd StartDuration) td.SmuggledGot {
		return td.SmuggledGot{
			Name: "ComputedEndDate",
			Got:  sd.StartDate.Add(sd.Duration),
		}
	}, td.Between(
		time.Date(2018, time.February, 17, 0, 0, 0, 0, time.UTC),
		time.Date(2018, time.February, 19, 0, 0, 0, 0, time.UTC)))
	fmt.Println(ok)
}
false
false
true
true
true
true
Example (Convert)

Code:

t := td.NewT(&testing.T{})

got := int64(123)

ok := t.Smuggle(got, func(n int64) int { return int(n) }, 123,
	"checks int64 got against an int value")
fmt.Println(ok)

ok = t.Smuggle("123", func(numStr string) (int, bool) {
	n, err := strconv.Atoi(numStr)
	return n, err == nil
}, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = t.Smuggle("123", func(numStr string) (int, bool, string) {
	n, err := strconv.Atoi(numStr)
	if err != nil {
		return 0, false, "string must contain a number"
	}
	return n, true, ""
}, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = t.Smuggle("123", func(numStr string) (int, error) {
	return strconv.Atoi(numStr)
}, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = t.Smuggle("123", strconv.Atoi, td.Between(120, 130),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)
true
true
true
true
true
Example (Field_path)

Code:

t := td.NewT(&testing.T{})

type Body struct {
	Name  string
	Value interface{}
}
type Request struct {
	Body *Body
}
type Transaction struct {
	Request
}
type ValueNum struct {
	Num int
}

got := &Transaction{
	Request: Request{
		Body: &Body{
			Name:  "test",
			Value: &ValueNum{Num: 123},
		},
	},
}

ok := t.Smuggle(got, func(t *Transaction) (int, error) {
	if t.Request.Body == nil ||
		t.Request.Body.Value == nil {
		return 0, errors.New("Request.Body or Request.Body.Value is nil")
	}
	if v, ok := t.Request.Body.Value.(*ValueNum); ok && v != nil {
		return v.Num, nil
	}
	return 0, errors.New("Request.Body.Value isn't *ValueNum or nil")
}, td.Between(100, 200))
fmt.Println("check Num by hand:", ok)

ok = t.Smuggle(got, "Request.Body.Value.Num", td.Between(100, 200))
fmt.Println("check Num using a fields-path:", ok)

ok = t.Smuggle(got, "Body.Value.Num", td.Between(100, 200))
fmt.Println("check Num using an other fields-path:", ok)
check Num by hand: true
check Num using a fields-path: true
check Num using an other fields-path: true
Example (Interface)

Code:

t := td.NewT(&testing.T{})

gotTime, err := time.Parse(time.RFC3339, "2018-05-23T12:13:14Z")
if err != nil {
	t.Fatal(err)
}

ok := t.Smuggle(gotTime, func(s fmt.Stringer) string {
	return s.String()
}, "2018-05-23 12:13:14 +0000 UTC")
fmt.Println("stringified time.Time OK:", ok)

// If got does not implement the fmt.Stringer interface, it fails
// without calling the Smuggle func
type MyTime time.Time
ok = t.Smuggle(MyTime(gotTime), func(s fmt.Stringer) string {
	fmt.Println("Smuggle func called!")
	return s.String()
}, "2018-05-23 12:13:14 +0000 UTC")
fmt.Println("stringified MyTime OK:", ok)
Example (Lax)

Code:

t := td.NewT(&testing.T{})

got := int(123)

ok := t.Smuggle(got, func(n int64) uint32 { return uint32(n) }, uint32(123))
fmt.Println("got int16(123) → smuggle via int64 → uint32(123):", ok)
got int16(123) → smuggle via int64 → uint32(123): true

func (*T) String

func (t *T) String(got interface{}, expected string, args ...interface{}) bool

String is a shortcut for:

t.Cmp(got, td.String(expected), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#String for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := "foobar"

ok := t.String(got, "foobar", "checks %s", got)
fmt.Println("using string:", ok)

ok = t.Cmp([]byte(got), td.String("foobar"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := td.NewT(&testing.T{})

got := errors.New("foobar")

ok := t.String(got, "foobar", "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := td.NewT(&testing.T{})

got := bytes.NewBufferString("foobar")

ok := t.String(got, "foobar", "checks %s", got)
fmt.Println(ok)
true

func (*T) Struct

func (t *T) Struct(got interface{}, model interface{}, expectedFields StructFields, args ...interface{}) bool

Struct is a shortcut for:

t.Cmp(got, td.Struct(model, expectedFields), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Struct for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

type Person struct {
	Name        string
	Age         int
	NumChildren int
}

got := Person{
	Name:        "Foobar",
	Age:         42,
	NumChildren: 3,
}

ok := t.Struct(got, Person{Name: "Foobar"}, td.StructFields{
	"Age": td.Between(40, 50),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = t.Struct(got, Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = t.Struct(&got, &Person{}, td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)

ok = t.Struct(&got, (*Person)(nil), td.StructFields{
	"Name":        "Foobar",
	"Age":         td.Between(40, 50),
	"NumChildren": td.Not(0),
},
	"checks %v is the right Person")
fmt.Println(ok)
true
true
true
true

func (*T) SubBagOf

func (t *T) SubBagOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool

SubBagOf is a shortcut for:

t.Cmp(got, td.SubBagOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubBagOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := t.SubBagOf(got, []interface{}{0, 0, 1, 1, 2, 2, 3, 3, 5, 5, 8, 8, 9, 9},
	"checks at least all items are present, in any order")
fmt.Println(ok)

ok = t.SubBagOf(got, []interface{}{0, 0, 1, 1, 2, 2, 3, 3, 5, 5, 8, 9, 9},
	"checks at least all items are present, in any order")
fmt.Println(ok)

got = []int{1, 3, 5, 2}

ok = t.SubBagOf(got, []interface{}{td.Between(0, 3), td.Between(0, 3), td.Between(0, 3), td.Between(0, 3), td.Gt(4), td.Gt(4)},
	"checks at least all items match, in any order with TestDeep operators")
fmt.Println(ok)

expected := []int{1, 2, 3, 5, 9, 8}
ok = t.SubBagOf(got, []interface{}{td.Flatten(expected)},
	"checks at least all expected items are present, in any order")
fmt.Println(ok)
true
false
true
true

func (*T) SubJSONOf

func (t *T) SubJSONOf(got interface{}, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

SubJSONOf is a shortcut for:

t.Cmp(got, td.SubJSONOf(expectedJSON, params...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubJSONOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Basic)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

ok := t.SubJSONOf(got, `{"age":42,"fullname":"Bob","gender":"male"}`, nil)
fmt.Println("check got with age then fullname:", ok)

ok = t.SubJSONOf(got, `{"fullname":"Bob","age":42,"gender":"male"}`, nil)
fmt.Println("check got with fullname then age:", ok)

ok = t.SubJSONOf(got, `
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42,    /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
  "gender":   "male" // This field is ignored as SubJSONOf
}`, nil)
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = t.SubJSONOf(got, `{"fullname":"Bob","gender":"male"}`, nil)
fmt.Println("check got without age field:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got without age field: false
Example (File)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender",
  "details":  {
    "city": "TestCity",
    "zip":  666
  }
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := t.SubJSONOf(got, filename, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = t.SubJSONOf(got, file, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
}

ok := t.SubJSONOf(got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{42, "Bob Foobar", "male"})
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = t.SubJSONOf(got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with numeric placeholders:", ok)

ok = t.SubJSONOf(got, `{"age": "$1", "fullname": "$2", "gender": "$3"}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = t.SubJSONOf(got, `{"age": $age, "fullname": $name, "gender": $gender}`, []interface{}{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar")), td.Tag("gender", td.NotEmpty())})
fmt.Println("check got with named placeholders:", ok)

ok = t.SubJSONOf(got, `{"age": $^NotZero, "fullname": $^NotEmpty, "gender": $^NotEmpty}`, nil)
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func (*T) SubMapOf

func (t *T) SubMapOf(got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool

SubMapOf is a shortcut for:

t.Cmp(got, td.SubMapOf(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubMapOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := td.NewT(&testing.T{})

got := map[string]int{"foo": 12, "bar": 42}

ok := t.SubMapOf(got, map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666},
	"checks map %v is included in expected keys/values", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := td.NewT(&testing.T{})

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42}

ok := t.SubMapOf(got, MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666},
	"checks typed map %v is included in expected keys/values", got)
fmt.Println(ok)

ok = t.SubMapOf(&got, &MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666},
	"checks pointed typed map %v is included in expected keys/values", got)
fmt.Println(ok)
true
true

func (*T) SubSetOf

func (t *T) SubSetOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool

SubSetOf is a shortcut for:

t.Cmp(got, td.SubSetOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SubSetOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := t.SubSetOf(got, []interface{}{1, 2, 3, 4, 5, 6, 7, 8},
	"checks at least all items are present, in any order, ignoring duplicates")
fmt.Println(ok)

ok = t.SubSetOf(got, []interface{}{td.Between(1, 4), 3, td.Between(2, 10), td.Gt(100)},
	"checks at least all items are present, in any order, ignoring duplicates")
fmt.Println(ok)

expected := []int{1, 2, 3, 4, 5, 6, 7, 8}
ok = t.SubSetOf(got, []interface{}{td.Flatten(expected)},
	"checks at least all expected items are present, in any order, ignoring duplicates")
fmt.Println(ok)
true
true
true

func (*T) SuperBagOf

func (t *T) SuperBagOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool

SuperBagOf is a shortcut for:

t.Cmp(got, td.SuperBagOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperBagOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := t.SuperBagOf(got, []interface{}{8, 5, 8},
	"checks the items are present, in any order")
fmt.Println(ok)

ok = t.SuperBagOf(got, []interface{}{td.Gt(5), td.Lte(2)},
	"checks at least 2 items of %v match", got)
fmt.Println(ok)

expected := []int{8, 5, 8}
ok = t.SuperBagOf(got, []interface{}{td.Flatten(expected)},
	"checks the expected items are present, in any order")
fmt.Println(ok)
true
true
true

func (*T) SuperJSONOf

func (t *T) SuperJSONOf(got interface{}, expectedJSON interface{}, params []interface{}, args ...interface{}) bool

SuperJSONOf is a shortcut for:

t.Cmp(got, td.SuperJSONOf(expectedJSON, params...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperJSONOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Basic)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

ok := t.SuperJSONOf(got, `{"age":42,"fullname":"Bob","gender":"male"}`, nil)
fmt.Println("check got with age then fullname:", ok)

ok = t.SuperJSONOf(got, `{"fullname":"Bob","age":42,"gender":"male"}`, nil)
fmt.Println("check got with fullname then age:", ok)

ok = t.SuperJSONOf(got, `
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42,    /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
  "gender":   "male" // The gender!
}`, nil)
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = t.SuperJSONOf(got, `{"fullname":"Bob","gender":"male","details":{}}`, nil)
fmt.Println("check got with details field:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got with details field: false
Example (File)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender"
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := t.SuperJSONOf(got, filename, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = t.SuperJSONOf(got, file, []interface{}{td.Tag("name", td.HasPrefix("Bob")), td.Tag("age", td.Between(40, 45)), td.Tag("gender", td.Re(`^(male|female)\z`))})
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := td.NewT(&testing.T{})

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

ok := t.SuperJSONOf(got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{42, "Bob Foobar", "male"})
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = t.SuperJSONOf(got, `{"age": $1, "fullname": $2, "gender": $3}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with numeric placeholders:", ok)

ok = t.SuperJSONOf(got, `{"age": "$1", "fullname": "$2", "gender": "$3"}`, []interface{}{td.Between(40, 45), td.HasSuffix("Foobar"), td.NotEmpty()})
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = t.SuperJSONOf(got, `{"age": $age, "fullname": $name, "gender": $gender}`, []interface{}{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar")), td.Tag("gender", td.NotEmpty())})
fmt.Println("check got with named placeholders:", ok)

ok = t.SuperJSONOf(got, `{"age": $^NotZero, "fullname": $^NotEmpty, "gender": $^NotEmpty}`, nil)
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func (*T) SuperMapOf

func (t *T) SuperMapOf(got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool

SuperMapOf is a shortcut for:

t.Cmp(got, td.SuperMapOf(model, expectedEntries), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperMapOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example (Map)

Code:

t := td.NewT(&testing.T{})

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := t.SuperMapOf(got, map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15)},
	"checks map %v contains at leat all expected keys/values", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := td.NewT(&testing.T{})

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := t.SuperMapOf(got, MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15)},
	"checks typed map %v contains at leat all expected keys/values", got)
fmt.Println(ok)

ok = t.SuperMapOf(&got, &MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15)},
	"checks pointed typed map %v contains at leat all expected keys/values",
	got)
fmt.Println(ok)
true
true

func (*T) SuperSetOf

func (t *T) SuperSetOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool

SuperSetOf is a shortcut for:

t.Cmp(got, td.SuperSetOf(expectedItems...), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#SuperSetOf for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := t.SuperSetOf(got, []interface{}{1, 2, 3},
	"checks the items are present, in any order and ignoring duplicates")
fmt.Println(ok)

ok = t.SuperSetOf(got, []interface{}{td.Gt(5), td.Lte(2)},
	"checks at least 2 items of %v match ignoring duplicates", got)
fmt.Println(ok)

expected := []int{1, 2, 3}
ok = t.SuperSetOf(got, []interface{}{td.Flatten(expected)},
	"checks the expected items are present, in any order and ignoring duplicates")
fmt.Println(ok)
true
true
true

func (*T) True

func (t *T) True(got interface{}, args ...interface{}) bool

True is shortcut for:

t.Cmp(got, true, args...)

Returns true if the test is OK, false if it fails.

t.True(IsAvailable(x), "x should be available")

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := true
ok := t.True(got, "check that got is true!")
fmt.Println(ok)

got = false
ok = t.True(got, "check that got is true!")
fmt.Println(ok)
true
false

func (*T) TruncTime

func (t *T) TruncTime(got interface{}, expectedTime interface{}, trunc time.Duration, args ...interface{}) bool

TruncTime is a shortcut for:

t.Cmp(got, td.TruncTime(expectedTime, trunc), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#TruncTime for details.

TruncTime() optional parameter "trunc" is here mandatory. 0 value should be passed to mimic its absence in original TruncTime() call.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

dateToTime := func(str string) time.Time {
	t, err := time.Parse(time.RFC3339Nano, str)
	if err != nil {
		panic(err)
	}
	return t
}

got := dateToTime("2018-05-01T12:45:53.123456789Z")

expected := dateToTime("2018-05-01T12:45:53Z")
ok := t.TruncTime(got, expected, time.Second,
	"checks date %v, truncated to the second", got)
fmt.Println(ok)

expected = dateToTime("2018-05-01T11:22:33.444444444Z")
ok = t.TruncTime(got, expected, 24*time.Hour,
	"checks date %v, truncated to the day", got)
fmt.Println(ok)

expected = dateToTime("2018-05-01T12:45:53.123456789Z")
ok = t.TruncTime(got, expected, 0,
	"checks date %v ignoring monotonic part", got)
fmt.Println(ok)
true
true
true

func (*T) UseEqual

func (t *T) UseEqual(enable ...bool) *T

UseEqual allows to use the Equal method on got (if it exists) or on any of its component to compare got and expected values.

The signature should be:

(A) Equal(B) bool

with B assignable to A.

See time.Time as an example of accepted Equal() method.

It returns a new instance of *T so does not alter the original t.

Note that t.UseEqual() acts as t.UseEqual(true).

func (*T) Values

func (t *T) Values(got interface{}, val interface{}, args ...interface{}) bool

Values is a shortcut for:

t.Cmp(got, td.Values(val), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Values for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

ok := t.Values(got, []int{1, 2, 3})
fmt.Println("All sorted values are found:", ok)

ok = t.Values(got, []int{3, 1, 2})
fmt.Println("All unsorted values are found:", ok)

ok = t.Values(got, td.Bag(3, 1, 2))
fmt.Println("All unsorted values are found, with the help of Bag operator:", ok)

ok = t.Values(got, td.ArrayEach(td.Between(1, 3)))
fmt.Println("Each value is between 1 and 3:", ok)
All sorted values are found: true
All unsorted values are found: false
All unsorted values are found, with the help of Bag operator: true
Each value is between 1 and 3: true

func (*T) Zero

func (t *T) Zero(got interface{}, args ...interface{}) bool

Zero is a shortcut for:

t.Cmp(got, td.Zero(), args...)

See https://pkg.go.dev/github.com/maxatome/go-testdeep/td#Zero for details.

Returns true if the test is OK, false if it fails.

"args..." are optional and allow to name the test. This name is used in case of failure to qualify the test. If len(args) > 1 and the first item of "args" is a string and contains a '%' rune then fmt.Fprintf is used to compose the name, else "args" are passed to fmt.Fprint. Do not forget it is the name of the test, not the reason of a potential failure.

Example

Code:

t := td.NewT(&testing.T{})

ok := t.Zero(0)
fmt.Println(ok)

ok = t.Zero(float64(0))
fmt.Println(ok)

ok = t.Zero(12)
fmt.Println(ok)

ok = t.Zero((map[string]int)(nil))
fmt.Println(ok)

ok = t.Zero(map[string]int{})
fmt.Println(ok)

ok = t.Zero(([]int)(nil))
fmt.Println(ok)

ok = t.Zero([]int{})
fmt.Println(ok)

ok = t.Zero([3]int{})
fmt.Println(ok)

ok = t.Zero([3]int{0, 1})
fmt.Println(ok)

ok = t.Zero(bytes.Buffer{})
fmt.Println(ok)

ok = t.Zero(&bytes.Buffer{})
fmt.Println(ok)

ok = t.Cmp(&bytes.Buffer{}, td.Ptr(td.Zero()))
fmt.Println(ok)
true
true
false
true
false
true
false
true
false
true
false
true

type TestDeep

type TestDeep interface {
	types.TestDeepStringer
	location.GetLocationer
	// Match checks "got" against the operator. It returns nil if it matches.
	Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error

	// HandleInvalid returns true if the operator is able to handle
	// untyped nil value. Otherwise the untyped nil value is handled
	// generically.
	HandleInvalid() bool
	// TypeBehind returns the type handled by the operator or nil if it
	// is not known. tdhttp helper uses it to know how to unmarshal HTTP
	// responses bodies before comparing them using the operator.
	TypeBehind() reflect.Type
	// contains filtered or unexported methods
}

TestDeep is the representation of a go-testdeep operator. It is not intended to be used directly, but through Cmp* functions.

func All

func All(expectedValues ...interface{}) TestDeep

All operator compares data against several expected values. During a match, all of them have to match to succeed. Consider it as a "AND" logical operator.

td.Cmp(t, "foobar", td.All(
  td.Len(6),
  td.HasPrefix("fo"),
  td.HasSuffix("ar"),
)) // succeeds

Note Flatten function can be used to group or reuse some values or operators and so avoid boring and inefficient copies:

stringOps := td.Flatten([]td.TestDeep{td.HasPrefix("fo"), td.HasSuffix("ar")})
td.Cmp(t, "foobar", td.All(
  td.Len(6),
  stringOps,
)) // succeeds

One can do the same with All operator itself:

stringOps := td.All(td.HasPrefix("fo"), td.HasSuffix("ar"))
td.Cmp(t, "foobar", td.All(
  td.Len(6),
  stringOps,
)) // succeeds

but if an error occurs in the nested All, the report is a bit more complex to read due to the nested level. Flatten does not create a new level, its slice is just flattened in the All parameters.

TypeBehind method can return a non-nil reflect.Type if all items known non-interface types are equal, or if only interface types are found (mostly issued from Isa()) and they are equal.

Example

Code:

t := &testing.T{}

got := "foo/bar"

ok := td.Cmp(t,
	got,
	td.All(td.Re("o/b"), td.HasSuffix("bar"), "foo/bar"),
	"checks value %s", got)
fmt.Println(ok)

ok = td.Cmp(t,
	got,
	td.All(td.Re("o/b"), td.HasSuffix("bar"), "fooX/Ybar"),
	"checks value %s", got)
fmt.Println(ok)

regOps := td.Flatten([]td.TestDeep{td.Re("o/b"), td.Re(`^fo`), td.Re(`ar$`)})
ok = td.Cmp(t,
	got,
	td.All(td.HasPrefix("foo"), regOps, td.HasSuffix("bar")),
	"checks all operators against value %s", got)
fmt.Println(ok)
true
false
true

func Any

func Any(expectedValues ...interface{}) TestDeep

Any operator compares data against several expected values. During a match, at least one of them has to match to succeed. Consider it as a "OR" logical operator.

td.Cmp(t, "foo", td.Any("bar", "foo", "zip")) // succeeds
td.Cmp(t, "foo", td.Any(
  td.Len(4),
  td.HasPrefix("f"),
  td.HasSuffix("z"),
)) // succeeds coz "f" prefix

Note Flatten function can be used to group or reuse some values or operators and so avoid boring and inefficient copies:

stringOps := td.Flatten([]td.TestDeep{td.HasPrefix("f"), td.HasSuffix("z")})
td.Cmp(t, "foobar", td.All(
  td.Len(4),
  stringOps,
)) // succeeds coz "f" prefix

TypeBehind method can return a non-nil reflect.Type if all items known non-interface types are equal, or if only interface types are found (mostly issued from Isa()) and they are equal.

Example

Code:

t := &testing.T{}

got := "foo/bar"

ok := td.Cmp(t, got, td.Any(td.Re("zip"), td.HasSuffix("bar")),
	"checks value %s", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Any(td.Re("zip"), td.HasSuffix("foo")),
	"checks value %s", got)
fmt.Println(ok)

regOps := td.Flatten([]td.TestDeep{td.Re("a/c"), td.Re(`^xx`), td.Re(`ar$`)})
ok = td.Cmp(t,
	got,
	td.Any(td.HasPrefix("xxx"), regOps, td.HasSuffix("zip")),
	"check at least one operator matches value %s", got)
fmt.Println(ok)
true
false
true

func Array

func Array(model interface{}, expectedEntries ArrayEntries) TestDeep

Array operator compares the contents of an array or a pointer on an array against the non-zero values of "model" (if any) and the values of "expectedEntries".

"model" must be the same type as compared data.

"expectedEntries" can be nil, if no zero entries are expected and no TestDeep operator are involved.

got := [3]int{12, 14, 17}
td.Cmp(t, got, td.Array([3]int{0, 14}, td.ArrayEntries{0: 12, 2: 17})) // succeeds
td.Cmp(t, got,
  td.Array([3]int{0, 14}, td.ArrayEntries{0: td.Gt(10), 2: td.Gt(15)})) // succeeds

TypeBehind method returns the reflect.Type of "model".

Example (Array)

Code:

t := &testing.T{}

got := [3]int{42, 58, 26}

ok := td.Cmp(t, got,
	td.Array([3]int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
	"checks array %v", got)
fmt.Println(ok)
true
Example (TypedArray)

Code:

t := &testing.T{}

type MyArray [3]int

got := MyArray{42, 58, 26}

ok := td.Cmp(t, got,
	td.Array(MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
	"checks typed array %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Array(&MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
	"checks pointer on typed array %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Array(&MyArray{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
	"checks pointer on typed array %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Array((*MyArray)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
	"checks pointer on typed array %v", got)
fmt.Println(ok)
true
true
true
true

func ArrayEach

func ArrayEach(expectedValue interface{}) TestDeep

ArrayEach operator has to be applied on arrays or slices or on pointers on array/slice. It compares each item of data array/slice against "expectedValue". During a match, all items have to match to succeed.

got := [3]string{"foo", "bar", "biz"}
td.Cmp(t, got, td.ArrayEach(td.Len(3)))         // succeeds
td.Cmp(t, got, td.ArrayEach(td.HasPrefix("b"))) // fails coz "foo"

Works on slices as well:

got := []Person{
  {Name: "Bob", Age: 42},
  {Name: "Alice", Age: 24},
}
td.Cmp(t, got, td.ArrayEach(
  td.Struct(Person{}, td.StructFields{
    Age: td.Between(20, 45),
  })),
) // succeeds, each Person has Age field between 20 and 45
Example (Array)

Code:

t := &testing.T{}

got := [3]int{42, 58, 26}

ok := td.Cmp(t, got, td.ArrayEach(td.Between(25, 60)),
	"checks each item of array %v is in [25 .. 60]", got)
fmt.Println(ok)
true
Example (Slice)

Code:

t := &testing.T{}

got := []int{42, 58, 26}

ok := td.Cmp(t, got, td.ArrayEach(td.Between(25, 60)),
	"checks each item of slice %v is in [25 .. 60]", got)
fmt.Println(ok)
true
Example (TypedArray)

Code:

t := &testing.T{}

type MyArray [3]int

got := MyArray{42, 58, 26}

ok := td.Cmp(t, got, td.ArrayEach(td.Between(25, 60)),
	"checks each item of typed array %v is in [25 .. 60]", got)
fmt.Println(ok)

ok = td.Cmp(t, &got, td.ArrayEach(td.Between(25, 60)),
	"checks each item of typed array pointer %v is in [25 .. 60]", got)
fmt.Println(ok)
true
true
Example (TypedSlice)

Code:

t := &testing.T{}

type MySlice []int

got := MySlice{42, 58, 26}

ok := td.Cmp(t, got, td.ArrayEach(td.Between(25, 60)),
	"checks each item of typed slice %v is in [25 .. 60]", got)
fmt.Println(ok)

ok = td.Cmp(t, &got, td.ArrayEach(td.Between(25, 60)),
	"checks each item of typed slice pointer %v is in [25 .. 60]", got)
fmt.Println(ok)
true
true

func Bag

func Bag(expectedItems ...interface{}) TestDeep

Bag operator compares the contents of an array or a slice (or a pointer on array/slice) without taking care of the order of items.

During a match, each expected item should match in the compared array/slice, and each array/slice item should be matched by an expected item to succeed.

td.Cmp(t, []int{1, 1, 2}, td.Bag(1, 1, 2))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.Bag(1, 2, 1))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.Bag(2, 1, 1))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.Bag(1, 2))       // fails, one 1 is missing
td.Cmp(t, []int{1, 1, 2}, td.Bag(1, 2, 1, 3)) // fails, 3 is missing

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.Bag(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

expected := []int{1, 2, 1}
td.Cmp(t, []int{1, 1, 2}, td.Bag(td.Flatten(expected))) // succeeds
// = td.Cmp(t, []int{1, 1, 2}, td.Bag(1, 2, 1))

exp1 := []int{5, 1, 1}
exp2 := []int{8, 42, 3}
td.Cmp(t, []int{1, 5, 1, 8, 42, 3, 3},
  td.Bag(td.Flatten(exp1), 3, td.Flatten(exp2))) // succeeds
// = td.Cmp(t, []int{1, 5, 1, 8, 42, 3, 3}, td.Bag(5, 1, 1, 3, 8, 42, 3))
Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.Cmp(t, got, td.Bag(1, 1, 2, 3, 5, 8, 8),
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.Cmp(t, got, td.Bag(1, 2, 3, 5, 8),
	"checks all items are present, in any order")
fmt.Println(ok)

got = []int{1, 3, 5, 8, 2}

ok = td.Cmp(t, got, td.Bag(1, 1, 2, 3, 5, 8, 8),
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.Cmp(t, got, td.Bag(1, 2, 3, 5, td.Gt(7)),
	"checks all items are present, in any order")
fmt.Println(ok)

expected := []int{1, 2, 3, 5}
ok = td.Cmp(t, got, td.Bag(td.Flatten(expected), td.Gt(7)),
	"checks all expected items are present, in any order")
fmt.Println(ok)
true
false
false
true
true

func Between

func Between(from interface{}, to interface{}, bounds ...BoundsKind) TestDeep

Between operator checks that data is between "from" and "to". "from" and "to" can be any numeric, string or time.Time (or assignable) value. "from" and "to" must be the same kind as the compared value if numeric, and the same type if string or time.Time (or assignable). "bounds" allows to specify whether bounds are included or not:

- BoundsInIn (default): between "from" and "to" both included
- BoundsInOut: between "from" included and "to" excluded
- BoundsOutIn: between "from" excluded and "to" included
- BoundsOutOut: between "from" and "to" both excluded

If "bounds" is missing, it defaults to BoundsInIn.

tc.Cmp(t, 17, td.Between(17, 20))               // succeeds, BoundsInIn by default
tc.Cmp(t, 17, td.Between(10, 17, BoundsInOut))  // fails
tc.Cmp(t, 17, td.Between(10, 17, BoundsOutIn))  // succeeds
tc.Cmp(t, 17, td.Between(17, 20, BoundsOutOut)) // fails

TypeBehind method returns the reflect.Type of "from" (same as the "to" one.)

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.Cmp(t, got, td.Between(154, 156),
	"checks %v is in [154 .. 156]", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between(154, 156, td.BoundsInIn),
	"checks %v is in [154 .. 156]", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between(154, 156, td.BoundsInOut),
	"checks %v is in [154 .. 156[", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between(154, 156, td.BoundsOutIn),
	"checks %v is in ]154 .. 156]", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between(154, 156, td.BoundsOutOut),
	"checks %v is in ]154 .. 156[", got)
fmt.Println(ok)
true
true
false
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.Cmp(t, got, td.Between("aaa", "abc"),
	`checks "%v" is in ["aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between("aaa", "abc", td.BoundsInIn),
	`checks "%v" is in ["aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between("aaa", "abc", td.BoundsInOut),
	`checks "%v" is in ["aaa" .. "abc"[`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between("aaa", "abc", td.BoundsOutIn),
	`checks "%v" is in ]"aaa" .. "abc"]`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Between("aaa", "abc", td.BoundsOutOut),
	`checks "%v" is in ]"aaa" .. "abc"[`, got)
fmt.Println(ok)
true
true
false
true
false

func Cap

func Cap(expectedCap interface{}) TestDeep

Cap is a smuggler operator. It takes data, applies cap() function on it and compares its result to "expectedCap". Of course, the compared value must be an array, a channel or a slice.

"expectedCap" can be an int value:

td.Cmp(t, gotSlice, td.Cap(12))

as well as an other operator:

td.Cmp(t, gotSlice, td.Cap(td.Between(3, 4)))
Example

Code:

t := &testing.T{}

got := make([]int, 0, 12)

ok := td.Cmp(t, got, td.Cap(12), "checks %v capacity is 12", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Cap(0), "checks %v capacity is 0", got)
fmt.Println(ok)

got = nil

ok = td.Cmp(t, got, td.Cap(0), "checks %v capacity is 0", got)
fmt.Println(ok)
true
false
true
Example (Operator)

Code:

t := &testing.T{}

got := make([]int, 0, 12)

ok := td.Cmp(t, got, td.Cap(td.Between(10, 12)),
	"checks %v capacity is in [10 .. 12]", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Cap(td.Gt(10)),
	"checks %v capacity is in [10 .. 12]", got)
fmt.Println(ok)
true
true

func Catch

func Catch(target interface{}, expectedValue interface{}) TestDeep

Catch is a smuggler operator. It allows to copy data in "target" on the fly before comparing it as usual against "expectedValue".

"target" must be a non-nil pointer and data should be assignable to its pointed type. If BeLax config flag is true or called under Lax (and so JSON) operator, data should be convertible to its pointer type.

var id int64
if td.Cmp(t, CreateRecord("test"),
  td.JSON(`{"id": $1, "name": "test"}`, td.Catch(&id, td.NotZero()))) {
  t.Logf("Created record ID is %d", id)
}

It is really useful when used with JSON operator and/or tdhttp helper.

var id int64
ta := tdhttp.NewTestAPI(t, api.Handler).
  PostJSON("/item", `{"name":"foo"}`).
  CmpStatus(http.StatusCreated).
  CmpJSONBody(td.JSON(`{"id": $1, "name": "foo"}`, td.Catch(&id, td.Gt(0))))
if !ta.Failed() {
  t.Logf("Created record ID is %d", id)
}

If you need to only catch data without comparing it, use Ignore operator as "expectedValue" as in:

var id int64
if td.Cmp(t, CreateRecord("test"),
  td.JSON(`{"id": $1, "name": "test"}`, td.Catch(&id, td.Ignore()))) {
  t.Logf("Created record ID is %d", id)
}
Example

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

var age int
ok := td.Cmp(t, got,
	td.JSON(`{"age":$1,"fullname":"Bob"}`,
		td.Catch(&age, td.Between(40, 45))))
fmt.Println("check got age+fullname:", ok)
fmt.Println("caught age:", age)
check got age+fullname: true
caught age: 42

func Code

func Code(fn interface{}) TestDeep

Code operator allows to check data using a custom function. So "fn" is a function that must take one parameter whose type must be the same as the type of the compared value.

"fn" can return a single bool kind value, telling that yes or no the custom test is successful:

td.Cmp(t, gotTime,
  td.Code(func(date time.Time) bool {
    return date.Year() == 2018
  }))

or two values (bool, string) kinds. The bool value has the same meaning as above, and the string value is used to describe the test when it fails:

td.Cmp(t, gotTime,
  td.Code(func(date time.Time) (bool, string) {
    if date.Year() == 2018 {
      return true, ""
    }
    return false, "year must be 2018"
  }))

or a single error value. If the returned error is nil, the test succeeded, else the error contains the reason of failure:

td.Cmp(t, gotJsonRawMesg,
  td.Code(func(b json.RawMessage) error {
    var c map[string]int
    err := json.Unmarshal(b, &c)
    if err != nil {
      return err
    }
    if c["test"] != 42 {
      return fmt.Errorf(`key "test" does not match 42`)
    }
    return nil
  }))

This operator allows to handle any specific comparison not handled by standard operators.

It is not recommended to call Cmp (or any other Cmp* functions or *T methods) inside the body of "fn", because of confusion produced by output in case of failure. When the data needs to be transformed before being compared again, Smuggle operator should be used instead.

TypeBehind method returns the reflect.Type of only parameter of "fn".

Example

Code:

t := &testing.T{}

got := "12"

ok := td.Cmp(t, got,
	td.Code(func(num string) bool {
		n, err := strconv.Atoi(num)
		return err == nil && n > 10 && n < 100
	}),
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Code(func(num string) (bool, string) {
		n, err := strconv.Atoi(num)
		if err != nil {
			return false, "not a number"
		}
		if n > 10 && n < 100 {
			return true, ""
		}
		return false, "not in ]10 .. 100["
	}),
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Code(func(num string) error {
		n, err := strconv.Atoi(num)
		if err != nil {
			return err
		}
		if n > 10 && n < 100 {
			return nil
		}
		return fmt.Errorf("%d not in ]10 .. 100[", n)
	}),
	"checks string `%s` contains a number and this number is in ]10 .. 100[",
	got)
fmt.Println(ok)
true
true
true

func Contains

func Contains(expectedValue interface{}) TestDeep

Contains is a smuggler operator to check if something is contained in another thing. Contains has to be applied on arrays, slices, maps or strings. It tries to be as smarter as possible.

If "expectedValue" is a TestDeep operator, each item of data array/slice/map/string (rune for strings) is compared to it. The use of a TestDeep operator as "expectedValue" works only in this way: item per item.

If data is a slice, and "expectedValue" has the same type, then "expectedValue" is searched as a sub-slice, otherwise "expectedValue" is compared to each slice value.

list := []int{12, 34, 28}
td.Cmp(t, list, td.Contains(34))                 // succeeds
td.Cmp(t, list, td.Contains(td.Between(30, 35))) // succeeds too
td.Cmp(t, list, td.Contains(35))                 // fails
td.Cmp(t, list, td.Contains([]int{34, 28}))      // succeeds

If data is an array or a map, each value is compared to "expectedValue". Map keys are not checked: see ContainsKey to check map keys existence.

hash := map[string]int{"foo": 12, "bar": 34, "zip": 28}
td.Cmp(t, hash, td.Contains(34))                 // succeeds
td.Cmp(t, hash, td.Contains(td.Between(30, 35))) // succeeds too
td.Cmp(t, hash, td.Contains(35))                 // fails

array := [...]int{12, 34, 28}
td.Cmp(t, array, td.Contains(34))                 // succeeds
td.Cmp(t, array, td.Contains(td.Between(30, 35))) // succeeds too
td.Cmp(t, array, td.Contains(35))                 // fails

If data is a string (or convertible), []byte (or convertible), error or fmt.Stringer interface (error interface is tested before fmt.Stringer), "expectedValue" can be a string, a []byte, a rune or a byte. In this case, it tests if the got string contains this expected string, []byte, rune or byte.

got := "foo bar"
td.Cmp(t, got, td.Contains('o'))                  // succeeds
td.Cmp(t, got, td.Contains(rune('o')))            // succeeds
td.Cmp(t, got, td.Contains(td.Between('n', 'p'))) // succeeds
td.Cmp(t, got, td.Contains("bar"))                // succeeds
td.Cmp(t, got, td.Contains([]byte("bar")))        // succeeds

td.Cmp(t, []byte("foobar"), td.Contains("ooba")) // succeeds

type Foobar string
td.Cmp(t, Foobar("foobar"), td.Contains("ooba")) // succeeds

err := errors.New("error!")
td.Cmp(t, err, td.Contains("ror")) // succeeds

bstr := bytes.NewBufferString("fmt.Stringer!")
td.Cmp(t, bstr, td.Contains("String")) // succeeds

Pitfall: if you want to check if 2 words are contained in got, don't do:

td.Cmp(t, "foobar", td.Contains(td.All("foo", "bar"))) // Bad!

as TestDeep operator All in Contains operates on each rune, so it does not work as expected, but do::

td.Cmp(t, "foobar", td.All(td.Contains("foo"), td.Contains("bar")))

When Contains(nil) is used, nil is automatically converted to a typed nil on the fly to avoid confusion (if the array/slice/map item type allows it of course.) So all following Cmp calls are equivalent (except the (*byte)(nil) one):

num := 123
list := []*int{&num, nil}
td.Cmp(t, list, td.Contains(nil))         // succeeds → (*int)(nil)
td.Cmp(t, list, td.Contains((*int)(nil))) // succeeds
td.Cmp(t, list, td.Contains(td.Nil()))    // succeeds
// But...
td.Cmp(t, list, td.Contains((*byte)(nil))) // fails: (*byte)(nil) ≠ (*int)(nil)

As well as these ones:

hash := map[string]*int{"foo": nil, "bar": &num}
td.Cmp(t, hash, td.Contains(nil))         // succeeds → (*int)(nil)
td.Cmp(t, hash, td.Contains((*int)(nil))) // succeeds
td.Cmp(t, hash, td.Contains(td.Nil()))    // succeeds
Example (ArraySlice)

Code:

t := &testing.T{}

ok := td.Cmp(t, [...]int{11, 22, 33, 44}, td.Contains(22))
fmt.Println("array contains 22:", ok)

ok = td.Cmp(t, [...]int{11, 22, 33, 44}, td.Contains(td.Between(20, 25)))
fmt.Println("array contains at least one item in [20 .. 25]:", ok)

ok = td.Cmp(t, []int{11, 22, 33, 44}, td.Contains(22))
fmt.Println("slice contains 22:", ok)

ok = td.Cmp(t, []int{11, 22, 33, 44}, td.Contains(td.Between(20, 25)))
fmt.Println("slice contains at least one item in [20 .. 25]:", ok)

ok = td.Cmp(t, []int{11, 22, 33, 44}, td.Contains([]int{22, 33}))
fmt.Println("slice contains the sub-slice [22, 33]:", ok)
array contains 22: true
array contains at least one item in [20 .. 25]: true
slice contains 22: true
slice contains at least one item in [20 .. 25]: true
slice contains the sub-slice [22, 33]: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.Cmp(t, got, td.Contains("oob"), "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = td.Cmp(t, got, td.Contains('b'), "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = td.Cmp(t, got, td.Contains(byte('a')), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = td.Cmp(t, got, td.Contains(td.Between('n', 'p')), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Map)

Code:

t := &testing.T{}

ok := td.Cmp(t,
	map[string]int{"foo": 11, "bar": 22, "zip": 33}, td.Contains(22))
fmt.Println("map contains value 22:", ok)

ok = td.Cmp(t,
	map[string]int{"foo": 11, "bar": 22, "zip": 33},
	td.Contains(td.Between(20, 25)))
fmt.Println("map contains at least one value in [20 .. 25]:", ok)
map contains value 22: true
map contains at least one value in [20 .. 25]: true
Example (Nil)

Code:

t := &testing.T{}

num := 123
got := [...]*int{&num, nil}

ok := td.Cmp(t, got, td.Contains(nil))
fmt.Println("array contains untyped nil:", ok)

ok = td.Cmp(t, got, td.Contains((*int)(nil)))
fmt.Println("array contains *int nil:", ok)

ok = td.Cmp(t, got, td.Contains(td.Nil()))
fmt.Println("array contains Nil():", ok)

ok = td.Cmp(t, got, td.Contains((*byte)(nil)))
fmt.Println("array contains *byte nil:", ok)
array contains untyped nil: true
array contains *int nil: true
array contains Nil(): true
array contains *byte nil: false
Example (String)

Code:

t := &testing.T{}

got := "foobar"

ok := td.Cmp(t, got, td.Contains("oob"), "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = td.Cmp(t, got, td.Contains([]byte("oob")), "checks %s", got)
fmt.Println("contains `oob` []byte:", ok)

ok = td.Cmp(t, got, td.Contains('b'), "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = td.Cmp(t, got, td.Contains(byte('a')), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = td.Cmp(t, got, td.Contains(td.Between('n', 'p')), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains `oob` []byte: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.Cmp(t, got, td.Contains("oob"), "checks %s", got)
fmt.Println("contains `oob` string:", ok)

ok = td.Cmp(t, got, td.Contains('b'), "checks %s", got)
fmt.Println("contains 'b' rune:", ok)

ok = td.Cmp(t, got, td.Contains(byte('a')), "checks %s", got)
fmt.Println("contains 'a' byte:", ok)

ok = td.Cmp(t, got, td.Contains(td.Between('n', 'p')), "checks %s", got)
fmt.Println("contains at least one character ['n' .. 'p']:", ok)
contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true

func ContainsKey

func ContainsKey(expectedValue interface{}) TestDeep

ContainsKey is a smuggler operator and works on maps only. It compares each key of map against "expectedValue".

hash := map[string]int{"foo": 12, "bar": 34, "zip": 28}
td.Cmp(t, hash, td.ContainsKey("foo"))             // succeeds
td.Cmp(t, hash, td.ContainsKey(td.HasPrefix("z"))) // succeeds
td.Cmp(t, hash, td.ContainsKey(td.HasPrefix("x"))) // fails

hnum := map[int]string{1: "foo", 42: "bar"}
td.Cmp(t, hash, td.ContainsKey(42))                 // succeeds
td.Cmp(t, hash, td.ContainsKey(td.Between(40, 45))) // succeeds

When ContainsKey(nil) is used, nil is automatically converted to a typed nil on the fly to avoid confusion (if the map key type allows it of course.) So all following Cmp calls are equivalent (except the (*byte)(nil) one):

num := 123
hnum := map[*int]bool{&num: true, nil: true}
td.Cmp(t, hnum, td.ContainsKey(nil))         // succeeds → (*int)(nil)
td.Cmp(t, hnum, td.ContainsKey((*int)(nil))) // succeeds
td.Cmp(t, hnum, td.ContainsKey(td.Nil()))    // succeeds
// But...
td.Cmp(t, hnum, td.ContainsKey((*byte)(nil))) // fails: (*byte)(nil) ≠ (*int)(nil)
Example

Code:

t := &testing.T{}

ok := td.Cmp(t,
	map[string]int{"foo": 11, "bar": 22, "zip": 33}, td.ContainsKey("foo"))
fmt.Println(`map contains key "foo":`, ok)

ok = td.Cmp(t,
	map[int]bool{12: true, 24: false, 42: true, 51: false},
	td.ContainsKey(td.Between(40, 50)))
fmt.Println("map contains at least a key in [40 .. 50]:", ok)

ok = td.Cmp(t,
	map[string]int{"FOO": 11, "bar": 22, "zip": 33},
	td.ContainsKey(td.Smuggle(strings.ToLower, "foo")))
fmt.Println(`map contains key "foo" without taking case into account:`, ok)
map contains key "foo": true
map contains at least a key in [40 .. 50]: true
map contains key "foo" without taking case into account: true
Example (Nil)

Code:

t := &testing.T{}

num := 1234
got := map[*int]bool{&num: false, nil: true}

ok := td.Cmp(t, got, td.ContainsKey(nil))
fmt.Println("map contains untyped nil key:", ok)

ok = td.Cmp(t, got, td.ContainsKey((*int)(nil)))
fmt.Println("map contains *int nil key:", ok)

ok = td.Cmp(t, got, td.ContainsKey(td.Nil()))
fmt.Println("map contains Nil() key:", ok)

ok = td.Cmp(t, got, td.ContainsKey((*byte)(nil)))
fmt.Println("map contains *byte nil key:", ok)
map contains untyped nil key: true
map contains *int nil key: true
map contains Nil() key: true
map contains *byte nil key: false

func Delay

func Delay(delayed func() TestDeep) TestDeep

Delay operator allows to delay the construction of an operator to the time it is used for the first time. Most of the time, it is used with helpers. See the example for a very simple use case.

Example

Code:

t := &testing.T{}

cmpNow := func(expected td.TestDeep) bool {
	time.Sleep(time.Microsecond)
	return td.Cmp(t, time.Now(), expected)
}

before := time.Now()

ok := cmpNow(td.Between(before, time.Now()))
fmt.Println("Between called before compare:", ok)

ok = cmpNow(td.Delay(func() td.TestDeep {
	return td.Between(before, time.Now())
}))
fmt.Println("Between delayed until compare:", ok)
Between called before compare: false
Between delayed until compare: true

func Empty

func Empty() TestDeep

Empty operator checks that an array, a channel, a map, a slice or a string is empty. As a special case (non-typed) nil, as well as nil channel, map or slice are considered empty.

Note that the compared data can be a pointer (of pointer of pointer etc.) on an array, a channel, a map, a slice or a string.

td.Cmp(t, "", td.Empty())                // succeeds
td.Cmp(t, map[string]bool{}, td.Empty()) // succeeds
td.Cmp(t, []string{"foo"}, td.Empty())   // fails
Example

Code:

t := &testing.T{}

ok := td.Cmp(t, nil, td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, (*int)(nil), td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, "", td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, 0, td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, (map[string]int)(nil), td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, map[string]int{}, td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, ([]int)(nil), td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, []int{}, td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, []int{3}, td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, [3]int{}, td.Empty())
fmt.Println(ok)
true
false
true
false
true
true
true
true
false
false
Example (Pointers)

Code:

t := &testing.T{}

type MySlice []int

ok := td.Cmp(t, MySlice{}, td.Empty())
fmt.Println(ok)

ok = td.Cmp(t, &MySlice{}, td.Empty())
fmt.Println(ok)

l1 := &MySlice{}
l2 := &l1
l3 := &l2
ok = td.Cmp(t, &l3, td.Empty())
fmt.Println(ok)

// But not for others types as:
type MyStruct struct {
	Value int
}

ok = td.Cmp(t, &MyStruct{}, td.Empty())
fmt.Println(ok)
true
true
true
false

func Gt

func Gt(minExpectedValue interface{}) TestDeep

Gt operator checks that data is greater than "minExpectedValue". "minExpectedValue" can be any numeric or time.Time (or assignable) value. "minExpectedValue" must be the same kind as the compared value if numeric, and the same type if time.Time (or assignable).

td.Cmp(t, 17, td.Gt(15))
before := time.Now()
td.Cmp(t, time.Now(), td.Gt(before))

TypeBehind method returns the reflect.Type of "minExpectedValue".

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.Cmp(t, got, td.Gt(155), "checks %v is > 155", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Gt(156), "checks %v is > 156", got)
fmt.Println(ok)
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.Cmp(t, got, td.Gt("abb"), `checks "%v" is > "abb"`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Gt("abc"), `checks "%v" is > "abc"`, got)
fmt.Println(ok)
true
false

func Gte

func Gte(minExpectedValue interface{}) TestDeep

Gte operator checks that data is greater or equal than "minExpectedValue". "minExpectedValue" can be any numeric or time.Time (or assignable) value. "minExpectedValue" must be the same kind as the compared value if numeric, and the same type if time.Time (or assignable).

td.Cmp(t, 17, td.Gte(17))
before := time.Now()
td.Cmp(t, time.Now(), td.Gte(before))

TypeBehind method returns the reflect.Type of "minExpectedValue".

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.Cmp(t, got, td.Gte(156), "checks %v is ≥ 156", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Gte(155), "checks %v is ≥ 155", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Gte(157), "checks %v is ≥ 157", got)
fmt.Println(ok)
true
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.Cmp(t, got, td.Gte("abc"), `checks "%v" is ≥ "abc"`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Gte("abb"), `checks "%v" is ≥ "abb"`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Gte("abd"), `checks "%v" is ≥ "abd"`, got)
fmt.Println(ok)
true
true
false

func HasPrefix

func HasPrefix(expected string) TestDeep

HasPrefix operator allows to compare the prefix of a string (or convertible), []byte (or convertible), error or fmt.Stringer interface (error interface is tested before fmt.Stringer).

td.Cmp(t, []byte("foobar"), td.HasPrefix("foo")) // succeeds

type Foobar string
td.Cmp(t, Foobar("foobar"), td.HasPrefix("foo")) // succeeds

err := errors.New("error!")
td.Cmp(t, err, td.HasPrefix("err")) // succeeds

bstr := bytes.NewBufferString("fmt.Stringer!")
td.Cmp(t, bstr, td.HasPrefix("fmt")) // succeeds
Example

Code:

t := &testing.T{}

got := "foobar"

ok := td.Cmp(t, got, td.HasPrefix("foo"), "checks %s", got)
fmt.Println("using string:", ok)

ok = td.Cmp(t, []byte(got), td.HasPrefix("foo"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.Cmp(t, got, td.HasPrefix("foo"), "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.Cmp(t, got, td.HasPrefix("foo"), "checks %s", got)
fmt.Println(ok)
true

func HasSuffix

func HasSuffix(expected string) TestDeep

HasSuffix operator allows to compare the suffix of a string (or convertible), []byte (or convertible), error or fmt.Stringer interface (error interface is tested before fmt.Stringer).

td.Cmp(t, []byte("foobar"), td.HasSuffix("bar")) // succeeds

type Foobar string
td.Cmp(t, Foobar("foobar"), td.HasSuffix("bar")) // succeeds

err := errors.New("error!")
td.Cmp(t, err, td.HasSuffix("!")) // succeeds

bstr := bytes.NewBufferString("fmt.Stringer!")
td.Cmp(t, bstr, td.HasSuffix("!")) // succeeds
Example

Code:

t := &testing.T{}

got := "foobar"

ok := td.Cmp(t, got, td.HasSuffix("bar"), "checks %s", got)
fmt.Println("using string:", ok)

ok = td.Cmp(t, []byte(got), td.HasSuffix("bar"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.Cmp(t, got, td.HasSuffix("bar"), "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.Cmp(t, got, td.HasSuffix("bar"), "checks %s", got)
fmt.Println(ok)
true

func Ignore

func Ignore() TestDeep

Ignore operator is always true, whatever data is. It is useful when comparing a slice with Slice and wanting to ignore some indexes, for example. Or comparing a struct with SStruct and wanting to ignore some fields:

td.Cmp(t, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    Age:      td.Between(40, 45),
    Children: td.Ignore(),
  }),
)
Example

Code:

t := &testing.T{}

ok := td.Cmp(t, []int{1, 2, 3},
	td.Slice([]int{}, td.ArrayEntries{
		0: 1,
		1: td.Ignore(),
		2: 3,
	}))
fmt.Println(ok)
true

func Isa

func Isa(model interface{}) TestDeep

Isa operator checks the data type or whether data implements an interface or not.

Typical type checks:

td.Cmp(t, time.Now(), td.Isa(time.Time{}))  // succeeds
td.Cmp(t, time.Now(), td.Isa(&time.Time{})) // fails, as not a *time.Time
td.Cmp(t, got, td.Isa(map[string]time.Time{}))

For interfaces, it is a bit more complicated, as:

fmt.Stringer(nil)

is not an interface, but just nil… To bypass this golang limitation, Isa accepts pointers on interfaces. So checking that data implements fmt.Stringer interface should be written as:

td.Cmp(t, bytes.Buffer{}, td.Isa((*fmt.Stringer)(nil))) // succeeds

Of course, in the latter case, if checked data type is *fmt.Stringer, Isa will match too (in fact before checking whether it implements fmt.Stringer or not).

TypeBehind method returns the reflect.Type of "model".

Example

Code:

t := &testing.T{}

type TstStruct struct {
	Field int
}

got := TstStruct{Field: 1}

ok := td.Cmp(t, got, td.Isa(TstStruct{}), "checks got is a TstStruct")
fmt.Println(ok)

ok = td.Cmp(t, got, td.Isa(&TstStruct{}),
	"checks got is a pointer on a TstStruct")
fmt.Println(ok)

ok = td.Cmp(t, &got, td.Isa(&TstStruct{}),
	"checks &got is a pointer on a TstStruct")
fmt.Println(ok)
true
false
true
Example (Interface)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.Cmp(t, got, td.Isa((*fmt.Stringer)(nil)),
	"checks got implements fmt.Stringer interface")
fmt.Println(ok)

errGot := fmt.Errorf("An error #%d occurred", 123)

ok = td.Cmp(t, errGot, td.Isa((*error)(nil)),
	"checks errGot is a *error or implements error interface")
fmt.Println(ok)

errGot = nil

ok = td.Cmp(t, errGot, td.Isa((*error)(nil)),
	"checks errGot is a *error or implements error interface")
fmt.Println(ok)

ok = td.Cmp(t, &errGot, td.Isa((*error)(nil)),
	"checks &errGot is a *error or implements error interface")
fmt.Println(ok)
true
true
false
true

func JSON

func JSON(expectedJSON interface{}, params ...interface{}) TestDeep

JSON operator allows to compare the JSON representation of data against "expectedJSON". "expectedJSON" can be a:

- string containing JSON data like `{"fullname":"Bob","age":42}`
- string containing a JSON filename, ending with ".json" (its
  content is ioutil.ReadFile before unmarshaling)
- []byte containing JSON data
- io.Reader stream containing JSON data (is ioutil.ReadAll before
  unmarshaling)

"expectedJSON" JSON value can contain placeholders. The "params" are for any placeholder parameters in "expectedJSON". "params" can contain TestDeep operators as well as raw values. A placeholder can be numeric like $2 or named like $name and always references an item in "params".

Numeric placeholders reference the n'th "operators" item (starting at 1). Named placeholders are used with Tag operator as follows:

td.Cmp(t, gotValue,
  td.JSON(`{"fullname": $name, "age": $2, "gender": $3}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

Note that placeholders can be double-quoted as in:

td.Cmp(t, gotValue,
  td.JSON(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

It makes no difference whatever the underlying type of the replaced item is (= double quoting a placeholder matching a number is not a problem). It is just a matter of taste, double-quoting placeholders can be preferred when the JSON data has to conform to the JSON specification, like when used in a ".json" file.

Note "expectedJSON" can be a []byte, JSON filename or io.Reader:

td.Cmp(t, gotValue, td.JSON("file.json", td.Between(12, 34)))
td.Cmp(t, gotValue, td.JSON([]byte(`[1, $1, 3]`), td.Between(12, 34)))
td.Cmp(t, gotValue, td.JSON(osFile, td.Between(12, 34)))

A JSON filename ends with ".json".

To avoid a legit "$" string prefix causes a bad placeholder error, just double it to escape it. Note it is only needed when the "$" is the first character of a string:

td.Cmp(t, gotValue,
  td.JSON(`{"fullname": "$name", "details": "$$info", "age": $2}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

For the "details" key, the raw value "$info" is expected, no placeholders are involved here.

Note that Lax mode is automatically enabled by JSON operator to simplify numeric tests.

Comments can be embedded in JSON data:

td.Cmp(t, gotValue,
  td.JSON(`
{
  // A guy properties:
  "fullname": "$name",  // The full name of the guy
  "details":  "$$info", // Literally "$info", thanks to "$" escape
  "age":      $2        /* The age of the guy:
                           - placeholder unquoted, but could be without
                             any change
                           - to demonstrate a multi-lines comment */
}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

Comments, like in go, have 2 forms. To quote the Go language specification:

- line comments start with the character sequence // and stop at the
  end of the line.
- multi-lines comments start with the character sequence /* and stop
  with the first subsequent character sequence */.

Last but not least, simple operators can be directly embedded in JSON data without requiring any placeholder but using directly $^OperatorName. They are operator shortcuts:

td.Cmp(t, gotValue, td.JSON(`{"id": $1}`, td.NotZero()))

can be written as:

td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero}`))

Unfortunately, only simple operators (in fact those which take no parameters) have shortcuts. They follow:

- Empty    → $^Empty
- Ignore   → $^Ignore
- NaN      → $^NaN
- Nil      → $^Nil
- NotEmpty → $^NotEmpty
- NotNaN   → $^NotNaN
- NotNil   → $^NotNil
- NotZero  → $^NotZero
- Zero     → $^Zero

TypeBehind method returns the reflect.Type of the "expectedJSON" json.Unmarshal'ed. So it can be bool, string, float64, []interface{}, map[string]interface{} or interface{} in case "expectedJSON" is "null".

Example (Basic)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

ok := td.Cmp(t, got, td.JSON(`{"age":42,"fullname":"Bob"}`))
fmt.Println("check got with age then fullname:", ok)

ok = td.Cmp(t, got, td.JSON(`{"fullname":"Bob","age":42}`))
fmt.Println("check got with fullname then age:", ok)

ok = td.Cmp(t, got, td.JSON(`
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42     /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
}`))
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = td.Cmp(t, got, td.JSON(`{"fullname":"Bob","age":42,"gender":"male"}`))
fmt.Println("check got with gender field:", ok)

ok = td.Cmp(t, got, td.JSON(`{"fullname":"Bob"}`))
fmt.Println("check got with fullname only:", ok)

ok = td.Cmp(t, true, td.JSON(`true`))
fmt.Println("check boolean got is true:", ok)

ok = td.Cmp(t, 42, td.JSON(`42`))
fmt.Println("check numeric got is 42:", ok)

got = nil
ok = td.Cmp(t, got, td.JSON(`null`))
fmt.Println("check nil got is null:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got with gender field: false
check got with fullname only: false
check boolean got is true: true
check numeric got is 42: true
check nil got is null: true
Example (File)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender"
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := td.Cmp(t, got,
	td.JSON(filename,
		td.Tag("name", td.HasPrefix("Bob")),
		td.Tag("age", td.Between(40, 45)),
		td.Tag("gender", td.Re(`^(male|female)\z`))))
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = td.Cmp(t, got,
	td.JSON(file,
		td.Tag("name", td.HasPrefix("Bob")),
		td.Tag("age", td.Between(40, 45)),
		td.Tag("gender", td.Re(`^(male|female)\z`))))
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
}

ok := td.Cmp(t, got, td.JSON(`{"age": $1, "fullname": $2}`, 42, "Bob Foobar"))
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = td.Cmp(t, got,
	td.JSON(`{"age": $1, "fullname": $2}`,
		td.Between(40, 45),
		td.HasSuffix("Foobar")))
fmt.Println("check got with numeric placeholders:", ok)

ok = td.Cmp(t, got,
	td.JSON(`{"age": "$1", "fullname": "$2"}`,
		td.Between(40, 45),
		td.HasSuffix("Foobar")))
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = td.Cmp(t, got,
	td.JSON(`{"age": $age, "fullname": $name}`,
		td.Tag("age", td.Between(40, 45)),
		td.Tag("name", td.HasSuffix("Foobar"))))
fmt.Println("check got with named placeholders:", ok)

ok = td.Cmp(t, got, td.JSON(`{"age": $^NotZero, "fullname": $^NotEmpty}`))
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func Keys

func Keys(val interface{}) TestDeep

Keys is a smuggler operator. It takes a map and compares its ordered keys to "val".

"val" can be a slice of items of the same type as the map keys:

got := map[string]bool{"c": true, "a": false, "b": true}
td.Cmp(t, got, td.Keys([]string{"a", "b", "c"})) // succeeds, keys sorted
td.Cmp(t, got, td.Keys([]string{"c", "a", "b"})) // fails as not sorted

as well as an other operator as Bag, for example, to test keys in an unsorted manner:

got := map[string]bool{"c": true, "a": false, "b": true}
td.Cmp(t, got, td.Keys(td.Bag("c", "a", "b"))) // succeeds
Example

Code:

t := &testing.T{}

got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

ok := td.Cmp(t, got, td.Keys([]string{"bar", "foo", "zip"}))
fmt.Println("All sorted keys are found:", ok)

ok = td.Cmp(t, got, td.Keys([]string{"zip", "bar", "foo"}))
fmt.Println("All unsorted keys are found:", ok)

ok = td.Cmp(t, got, td.Keys(td.Bag("zip", "bar", "foo")))
fmt.Println("All unsorted keys are found, with the help of Bag operator:", ok)

ok = td.Cmp(t, got, td.Keys(td.ArrayEach(td.Len(3))))
fmt.Println("Each key is 3 bytes long:", ok)
All sorted keys are found: true
All unsorted keys are found: false
All unsorted keys are found, with the help of Bag operator: true
Each key is 3 bytes long: true

func Lax

func Lax(expectedValue interface{}) TestDeep

Lax is a smuggler operator, it temporarily enables the BeLax config flag before letting the comparison process continue its course.

It is more commonly used as CmpLax function than as an operator. It could be used when, for example, an operator is constructed once but applied to different, but compatible types as in:

bw := td.Between(20, 30)
intValue := 21
floatValue := 21.89
td.Cmp(t, intValue, bw)           // no need to be lax here: same int types
td.Cmp(t, floatValue, td.Lax(bw)) // be lax please, as float64 ≠ int

Note that in the latter case, CmpLax() could be used as well:

td.CmpLax(t, floatValue, bw)

TypeBehind method returns the greatest convertible or more common reflect.Type of "expectedValue" if it is a base type (bool, int*, uint*, float*, complex*, string), the reflect.Type of "expectedValue" otherwise, except if "expectedValue" is a TestDeep operator. In this case, it delegates TypeBehind() to the operator.

Example

Code:

t := &testing.T{}

gotInt64 := int64(1234)
gotInt32 := int32(1235)

type myInt uint16
gotMyInt := myInt(1236)

expected := td.Between(1230, 1240)

ok := td.Cmp(t, gotInt64, td.Lax(expected))
fmt.Println("int64 got between ints [1230 .. 1240]:", ok)

ok = td.Cmp(t, gotInt32, td.Lax(expected))
fmt.Println("int32 got between ints [1230 .. 1240]:", ok)

ok = td.Cmp(t, gotMyInt, td.Lax(expected))
fmt.Println("myInt got between ints [1230 .. 1240]:", ok)
int64 got between ints [1230 .. 1240]: true
int32 got between ints [1230 .. 1240]: true
myInt got between ints [1230 .. 1240]: true

func Len

func Len(expectedLen interface{}) TestDeep

Len is a smuggler operator. It takes data, applies len() function on it and compares its result to "expectedLen". Of course, the compared value must be an array, a channel, a map, a slice or a string.

"expectedLen" can be an int value:

td.Cmp(t, gotSlice, td.Len(12))

as well as an other operator:

td.Cmp(t, gotSlice, td.Len(td.Between(3, 4)))
Example (Map)

Code:

t := &testing.T{}

got := map[int]bool{11: true, 22: false, 33: false}

ok := td.Cmp(t, got, td.Len(3), "checks %v len is 3", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Len(0), "checks %v len is 0", got)
fmt.Println(ok)

got = nil

ok = td.Cmp(t, got, td.Len(0), "checks %v len is 0", got)
fmt.Println(ok)
true
false
true
Example (OperatorMap)

Code:

t := &testing.T{}

got := map[int]bool{11: true, 22: false, 33: false}

ok := td.Cmp(t, got, td.Len(td.Between(3, 8)),
	"checks %v len is in [3 .. 8]", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Len(td.Gte(3)), "checks %v len is ≥ 3", got)
fmt.Println(ok)
true
true
Example (OperatorSlice)

Code:

t := &testing.T{}

got := []int{11, 22, 33}

ok := td.Cmp(t, got, td.Len(td.Between(3, 8)),
	"checks %v len is in [3 .. 8]", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Len(td.Lt(5)), "checks %v len is < 5", got)
fmt.Println(ok)
true
true
Example (Slice)

Code:

t := &testing.T{}

got := []int{11, 22, 33}

ok := td.Cmp(t, got, td.Len(3), "checks %v len is 3", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Len(0), "checks %v len is 0", got)
fmt.Println(ok)

got = nil

ok = td.Cmp(t, got, td.Len(0), "checks %v len is 0", got)
fmt.Println(ok)
true
false
true

func Lt

func Lt(maxExpectedValue interface{}) TestDeep

Lt operator checks that data is lesser than "maxExpectedValue". "maxExpectedValue" can be any numeric or time.Time (or assignable) value. "maxExpectedValue" must be the same kind as the compared value if numeric, and the same type if time.Time (or assignable).

td.Cmp(t, 17, td.Lt(19))
before := time.Now()
td.Cmp(t, before, td.Lt(time.Now()))

TypeBehind method returns the reflect.Type of "maxExpectedValue".

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.Cmp(t, got, td.Lt(157), "checks %v is < 157", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Lt(156), "checks %v is < 156", got)
fmt.Println(ok)
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.Cmp(t, got, td.Lt("abd"), `checks "%v" is < "abd"`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Lt("abc"), `checks "%v" is < "abc"`, got)
fmt.Println(ok)
true
false

func Lte

func Lte(maxExpectedValue interface{}) TestDeep

Lte operator checks that data is lesser or equal than "maxExpectedValue". "maxExpectedValue" can be any numeric or time.Time (or assignable) value. "maxExpectedValue" must be the same kind as the compared value if numeric, and the same type if time.Time (or assignable).

td.Cmp(t, 17, td.Lte(17))
before := time.Now()
td.Cmp(t, before, td.Lt(time.Now()))

TypeBehind method returns the reflect.Type of "maxExpectedValue".

Example (Int)

Code:

t := &testing.T{}

got := 156

ok := td.Cmp(t, got, td.Lte(156), "checks %v is ≤ 156", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Lte(157), "checks %v is ≤ 157", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Lte(155), "checks %v is ≤ 155", got)
fmt.Println(ok)
true
true
false
Example (String)

Code:

t := &testing.T{}

got := "abc"

ok := td.Cmp(t, got, td.Lte("abc"), `checks "%v" is ≤ "abc"`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Lte("abd"), `checks "%v" is ≤ "abd"`, got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Lte("abb"), `checks "%v" is ≤ "abb"`, got)
fmt.Println(ok)
true
true
false

func Map

func Map(model interface{}, expectedEntries MapEntries) TestDeep

Map operator compares the contents of a map against the non-zero values of "model" (if any) and the values of "expectedEntries".

"model" must be the same type as compared data.

"expectedEntries" can be nil, if no zero entries are expected and no TestDeep operator are involved.

During a match, all expected entries must be found and all data entries must be expected to succeed.

got := map[string]string{
  "foo": "test",
  "bar": "wizz",
  "zip": "buzz",
}
td.Cmp(t, got, td.Map(
  map[string]string{
    "foo": "test",
    "bar": "wizz",
  },
  td.MapEntries{
    "zip": td.HasSuffix("zz"),
  }),
) // succeeds

TypeBehind method returns the reflect.Type of "model".

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := td.Cmp(t, got,
	td.Map(map[string]int{"bar": 42},
		td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()}),
	"checks map %v", got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Map(map[string]int{},
		td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()}),
	"checks map %v", got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Map((map[string]int)(nil),
		td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()}),
	"checks map %v", got)
fmt.Println(ok)
true
true
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := td.Cmp(t, got,
	td.Map(MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()}),
	"checks typed map %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Map(&MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": td.Ignore()}),
	"checks pointer on typed map %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Map(&MyMap{}, td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()}),
	"checks pointer on typed map %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Map((*MyMap)(nil), td.MapEntries{"bar": 42, "foo": td.Lt(15), "zip": td.Ignore()}),
	"checks pointer on typed map %v", got)
fmt.Println(ok)
true
true
true
true

func MapEach

func MapEach(expectedValue interface{}) TestDeep

MapEach operator has to be applied on maps. It compares each value of data map against expected value. During a match, all values have to match to succeed.

got := map[string]string{"test": "foo", "buzz": "bar"}
td.Cmp(t, got, td.MapEach("bar"))     // fails, coz "foo" ≠ "bar"
td.Cmp(t, got, td.MapEach(td.Len(3))) // succeeds as values are 3 chars long
Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := td.Cmp(t, got, td.MapEach(td.Between(10, 90)),
	"checks each value of map %v is in [10 .. 90]", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := td.Cmp(t, got, td.MapEach(td.Between(10, 90)),
	"checks each value of typed map %v is in [10 .. 90]", got)
fmt.Println(ok)

ok = td.Cmp(t, &got, td.MapEach(td.Between(10, 90)),
	"checks each value of typed map pointer %v is in [10 .. 90]", got)
fmt.Println(ok)
true
true

func N

func N(num interface{}, tolerance ...interface{}) TestDeep

N operator compares a numeric data against "num" ± "tolerance". If "tolerance" is missing, it defaults to 0. "num" and "tolerance" must be the same kind as the compared value.

td.Cmp(t, 12.2, td.N(12., 0.3)) // succeeds
td.Cmp(t, 12.2, td.N(12., 0.1)) // fails

TypeBehind method returns the reflect.Type of "num".

Example

Code:

t := &testing.T{}

got := 1.12345

ok := td.Cmp(t, got, td.N(1.1234, 0.00006),
	"checks %v = 1.1234 ± 0.00006", got)
fmt.Println(ok)
true

func NaN

func NaN() TestDeep

NaN operator checks that data is a float and is not-a-number.

got := math.NaN()
td.Cmp(t, got, td.NaN()) // succeeds
td.Cmp(t, 4.2, td.NaN()) // fails
Example (Float32)

Code:

t := &testing.T{}

got := float32(math.NaN())

ok := td.Cmp(t, got, td.NaN(),
	"checks %v is not-a-number", got)

fmt.Println("float32(math.NaN()) is float32 not-a-number:", ok)

got = 12

ok = td.Cmp(t, got, td.NaN(),
	"checks %v is not-a-number", got)

fmt.Println("float32(12) is float32 not-a-number:", ok)
float32(math.NaN()) is float32 not-a-number: true
float32(12) is float32 not-a-number: false
Example (Float64)

Code:

t := &testing.T{}

got := math.NaN()

ok := td.Cmp(t, got, td.NaN(),
	"checks %v is not-a-number", got)

fmt.Println("math.NaN() is not-a-number:", ok)

got = 12

ok = td.Cmp(t, got, td.NaN(),
	"checks %v is not-a-number", got)

fmt.Println("float64(12) is not-a-number:", ok)

func Nil

func Nil() TestDeep

Nil operator checks that data is nil (or is a non-nil interface, but containing a nil pointer.)

var got *int
td.Cmp(t, got, td.Nil())    // succeeds
td.Cmp(t, got, nil)         // fails as (*int)(nil) ≠ untyped nil
td.Cmp(t, got, (*int)(nil)) // succeeds

but:

var got fmt.Stringer = (*bytes.Buffer)(nil)
td.Cmp(t, got, td.Nil()) // succeeds
td.Cmp(t, got, nil)      // fails, as the interface is not nil
got = nil
td.Cmp(t, got, nil) // succeeds
Example

Code:

t := &testing.T{}

var got fmt.Stringer // interface

ok := td.Cmp(t, got, nil)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Nil())
fmt.Println(ok)

got = (*bytes.Buffer)(nil)

ok = td.Cmp(t, got, nil)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Nil())
fmt.Println(ok)
true
true
false
true

func None

func None(notExpectedValues ...interface{}) TestDeep

None operator compares data against several not expected values. During a match, none of them have to match to succeed.

td.Cmp(t, 12, td.None(8, 10, 14))     // succeeds
td.Cmp(t, 12, td.None(8, 10, 12, 14)) // fails

Note Flatten function can be used to group or reuse some values or operators and so avoid boring and inefficient copies:

prime := td.Flatten([]int{1, 2, 3, 5, 7, 11, 13})
even := td.Flatten([]int{2, 4, 6, 8, 10, 12, 14})
td.Cmp(t, 9, td.None(prime, even)) // succeeds
Example

Code:

t := &testing.T{}

got := 18

ok := td.Cmp(t, got, td.None(0, 10, 20, 30, td.Between(100, 199)),
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

got = 20

ok = td.Cmp(t, got, td.None(0, 10, 20, 30, td.Between(100, 199)),
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

got = 142

ok = td.Cmp(t, got, td.None(0, 10, 20, 30, td.Between(100, 199)),
	"checks %v is non-null, and ≠ 10, 20 & 30, and not in [100-199]", got)
fmt.Println(ok)

prime := td.Flatten([]int{1, 2, 3, 5, 7, 11, 13})
even := td.Flatten([]int{2, 4, 6, 8, 10, 12, 14})
for _, got := range [...]int{9, 3, 8, 15} {
	ok = td.Cmp(t, got, td.None(prime, even, td.Gt(14)),
		"checks %v is not prime number, nor an even number and not > 14")
	fmt.Printf("%d → %t\n", got, ok)
}
true
false
false
9 → true
3 → false
8 → false
15 → false

func Not

func Not(notExpected interface{}) TestDeep

Not operator compares data against the not expected value. During a match, it must not match to succeed.

Not is the same operator as None() with only one argument. It is provided as a more readable function when only one argument is needed.

td.Cmp(t, 12, td.Not(10)) // succeeds
td.Cmp(t, 12, td.Not(12)) // fails
Example

Code:

t := &testing.T{}

got := 42

ok := td.Cmp(t, got, td.Not(0), "checks %v is non-null", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.Not(td.Between(10, 30)),
	"checks %v is not in [10 .. 30]", got)
fmt.Println(ok)

got = 0

ok = td.Cmp(t, got, td.Not(0), "checks %v is non-null", got)
fmt.Println(ok)
true
true
false

func NotAny

func NotAny(notExpectedItems ...interface{}) TestDeep

NotAny operator checks that the contents of an array or a slice (or a pointer on array/slice) does not contain any of "notExpectedItems".

td.Cmp(t, []int{1}, td.NotAny(1, 2, 3)) // fails
td.Cmp(t, []int{5}, td.NotAny(1, 2, 3)) // succeeds

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.NotAny(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

notExpected := []int{2, 1}
td.Cmp(t, []int{4, 4, 3, 8}, td.NotAny(td.Flatten(notExpected))) // succeeds
// = td.Cmp(t, []int{4, 4, 3, 8}, td.NotAny(2, 1))

notExp1 := []int{2, 1}
notExp2 := []int{5, 8}
td.Cmp(t, []int{4, 4, 42, 8},
  td.NotAny(td.Flatten(notExp1), 3, td.Flatten(notExp2))) // succeeds
// = td.Cmp(t, []int{4, 4, 42, 8}, td.NotAny(2, 1, 3, 5, 8))

Beware that NotAny(…) is not equivalent to Not(Any(…)) but is like Not(SuperSet(…)).

Example

Code:

t := &testing.T{}

got := []int{4, 5, 9, 42}

ok := td.Cmp(t, got, td.NotAny(3, 6, 8, 41, 43),
	"checks %v contains no item listed in NotAny()", got)
fmt.Println(ok)

ok = td.Cmp(t, got, td.NotAny(3, 6, 8, 42, 43),
	"checks %v contains no item listed in NotAny()", got)
fmt.Println(ok)

notExpected := []int{3, 6, 8, 41, 43}
ok = td.Cmp(t, got, td.NotAny(td.Flatten(notExpected)),
	"checks %v contains no item listed in notExpected", got)
fmt.Println(ok)
true
false
true

func NotEmpty

func NotEmpty() TestDeep

NotEmpty operator checks that an array, a channel, a map, a slice or a string is not empty. As a special case (non-typed) nil, as well as nil channel, map or slice are considered empty.

Note that the compared data can be a pointer (of pointer of pointer etc.) on an array, a channel, a map, a slice or a string.

td.Cmp(t, "", td.NotEmpty())                // fails
td.Cmp(t, map[string]bool{}, td.NotEmpty()) // fails
td.Cmp(t, []string{"foo"}, td.NotEmpty())   // succeeds
Example

Code:

t := &testing.T{}

ok := td.Cmp(t, nil, td.NotEmpty())
fmt.Println(ok)

ok = td.Cmp(t, "foobar", td.NotEmpty())
fmt.Println(ok)

ok = td.Cmp(t, 0, td.NotEmpty())
fmt.Println(ok)

ok = td.Cmp(t, map[string]int{"foobar": 42}, td.NotEmpty())
fmt.Println(ok)

ok = td.Cmp(t, []int{1}, td.NotEmpty())
fmt.Println(ok)

ok = td.Cmp(t, [3]int{}, td.NotEmpty())
fmt.Println(ok)
false
true
false
true
true
true
Example (Pointers)

Code:

t := &testing.T{}

type MySlice []int

ok := td.Cmp(t, MySlice{12}, td.NotEmpty())
fmt.Println(ok)

ok = td.Cmp(t, &MySlice{12}, td.NotEmpty())
fmt.Println(ok)

l1 := &MySlice{12}
l2 := &l1
l3 := &l2
ok = td.Cmp(t, &l3, td.NotEmpty())
fmt.Println(ok)

// But not for others types as:
type MyStruct struct {
	Value int
}

ok = td.Cmp(t, &MyStruct{}, td.NotEmpty())
fmt.Println(ok)
true
true
true
false

func NotNaN

func NotNaN() TestDeep

NotNaN operator checks that data is a float and is not not-a-number.

got := math.NaN()
td.Cmp(t, got, td.NotNaN()) // fails
td.Cmp(t, 4.2, td.NotNaN()) // succeeds
td.Cmp(t, 4, td.NotNaN())   // fails, as 4 is not a float
Example (Float32)

Code:

t := &testing.T{}

got := float32(math.NaN())

ok := td.Cmp(t, got, td.NotNaN(),
	"checks %v is not-a-number", got)

fmt.Println("float32(math.NaN()) is NOT float32 not-a-number:", ok)

got = 12

ok = td.Cmp(t, got, td.NotNaN(),
	"checks %v is not-a-number", got)

fmt.Println("float32(12) is NOT float32 not-a-number:", ok)
float32(math.NaN()) is NOT float32 not-a-number: false
float32(12) is NOT float32 not-a-number: true
Example (Float64)

Code:

t := &testing.T{}

got := math.NaN()

ok := td.Cmp(t, got, td.NotNaN(),
	"checks %v is not-a-number", got)

fmt.Println("math.NaN() is not-a-number:", ok)

got = 12

ok = td.Cmp(t, got, td.NotNaN(),
	"checks %v is not-a-number", got)

fmt.Println("float64(12) is not-a-number:", ok)

func NotNil

func NotNil() TestDeep

NotNil operator checks that data is not nil (or is a non-nil interface, containing a non-nil pointer.)

got := &Person{}
td.Cmp(t, got, td.NotNil()) // succeeds
td.Cmp(t, got, td.Not(nil)) // succeeds too, but be careful it is first
// because of got type *Person ≠ untyped nil so prefer NotNil()

but:

var got fmt.Stringer = (*bytes.Buffer)(nil)
td.Cmp(t, got, td.NotNil()) // fails
td.Cmp(t, got, td.Not(nil)) // succeeds, as the interface is not nil
Example

Code:

t := &testing.T{}

var got fmt.Stringer = &bytes.Buffer{}

ok := td.Cmp(t, got, td.Not(nil))
fmt.Println(ok)

ok = td.Cmp(t, got, td.NotNil())
fmt.Println(ok)

got = (*bytes.Buffer)(nil)

ok = td.Cmp(t, got, td.Not(nil))
fmt.Println(ok)

ok = td.Cmp(t, got, td.NotNil())
fmt.Println(ok)
true
true
true
false

func NotZero

func NotZero() TestDeep

NotZero operator checks that data is not zero regarding its type.

- nil is the zero value of pointers, maps, slices, channels and functions;
- 0 is the zero value of numbers;
- "" is the 0 value of strings;
- false is the zero value of booleans;
- zero value of structs is the struct with no fields initialized.

Beware that:

td.Cmp(t, AnyStruct{}, td.NotZero())          // is false
td.Cmp(t, &AnyStruct{}, td.NotZero())         // is true, coz pointer ≠ nil
td.Cmp(t, &AnyStruct{}, td.Ptr(td.NotZero())) // is false
Example

Code:

t := &testing.T{}

ok := td.Cmp(t, 0, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, float64(0), td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, 12, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, (map[string]int)(nil), td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, map[string]int{}, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, ([]int)(nil), td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, []int{}, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, [3]int{}, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, [3]int{0, 1}, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, bytes.Buffer{}, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, &bytes.Buffer{}, td.NotZero())
fmt.Println(ok)

ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.NotZero()))
fmt.Println(ok)
false
false
true
false
true
false
true
false
true
false
true
false

func PPtr

func PPtr(val interface{}) TestDeep

PPtr is a smuggler operator. It takes the address of the address of data and compares it to "val".

"val" depends on data type. For example, if the compared data is an **int, one can have:

num := 12
pnum = &num
td.Cmp(t, &pnum, td.PPtr(12)) // succeeds

as well as an other operator:

num := 3
pnum = &num
td.Cmp(t, &pnum, td.PPtr(td.Between(3, 4))) // succeeds

It is more efficient and shorter to write than:

td.Cmp(t, &pnum, td.Ptr(td.Ptr(val))) // succeeds too

TypeBehind method returns the reflect.Type of a pointer on a pointer on "val", except if "val" is a TestDeep operator. In this case, it delegates TypeBehind() to the operator and returns the reflect.Type of a pointer on a pointer on the returned value (if non-nil of course).

Example

Code:

t := &testing.T{}

num := 12
got := &num

ok := td.Cmp(t, &got, td.PPtr(12))
fmt.Println(ok)

ok = td.Cmp(t, &got, td.PPtr(td.Between(4, 15)))
fmt.Println(ok)
true
true

func Ptr

func Ptr(val interface{}) TestDeep

Ptr is a smuggler operator. It takes the address of data and compares it to "val".

"val" depends on data type. For example, if the compared data is an *int, one can have:

num := 12
td.Cmp(t, &num, td.Ptr(12)) // succeeds

as well as an other operator:

num := 3
td.Cmp(t, &num, td.Ptr(td.Between(3, 4)))

TypeBehind method returns the reflect.Type of a pointer on "val", except if "val" is a TestDeep operator. In this case, it delegates TypeBehind() to the operator and returns the reflect.Type of a pointer on the returned value (if non-nil of course).

Example

Code:

t := &testing.T{}

got := 12

ok := td.Cmp(t, &got, td.Ptr(12))
fmt.Println(ok)

ok = td.Cmp(t, &got, td.Ptr(td.Between(4, 15)))
fmt.Println(ok)
true
true

func Re

func Re(reg interface{}, capture ...interface{}) TestDeep

Re operator allows to apply a regexp on a string (or convertible), []byte, error or fmt.Stringer interface (error interface is tested before fmt.Stringer.)

"reg" is the regexp. It can be a string that is automatically compiled using regexp.MustCompile, or a *regexp.Regexp.

Optional "capture" parameter can be used to match the contents of regexp groups. Groups are presented as a []string or [][]byte depending the original matched data. Note that an other operator can be used here.

td.Cmp(t, "foobar zip!", td.Re(`^foobar`)) // succeeds
td.Cmp(t, "John Doe",
  td.Re(`^(\w+) (\w+)`, []string{"John", "Doe"})) // succeeds
td.Cmp(t, "John Doe",
  td.Re(`^(\w+) (\w+)`, td.Bag("Doe", "John"))) // succeeds
Example

Code:

t := &testing.T{}

got := "foo bar"
ok := td.Cmp(t, got, td.Re("(zip|bar)$"), "checks value %s", got)
fmt.Println(ok)

got = "bar foo"
ok = td.Cmp(t, got, td.Re("(zip|bar)$"), "checks value %s", got)
fmt.Println(ok)
true
false
Example (Capture)

Code:

t := &testing.T{}

got := "foo bar biz"
ok := td.Cmp(t, got, td.Re(`^(\w+) (\w+) (\w+)$`, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)

got = "foo bar! biz"
ok = td.Cmp(t, got, td.Re(`^(\w+) (\w+) (\w+)$`, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (Compiled)

Code:

t := &testing.T{}

expected := regexp.MustCompile("(zip|bar)$")

got := "foo bar"
ok := td.Cmp(t, got, td.Re(expected), "checks value %s", got)
fmt.Println(ok)

got = "bar foo"
ok = td.Cmp(t, got, td.Re(expected), "checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCapture)

Code:

t := &testing.T{}

expected := regexp.MustCompile(`^(\w+) (\w+) (\w+)$`)

got := "foo bar biz"
ok := td.Cmp(t, got, td.Re(expected, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)

got = "foo bar! biz"
ok = td.Cmp(t, got, td.Re(expected, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledError)

Code:

t := &testing.T{}

expected := regexp.MustCompile("(zip|bar)$")

got := errors.New("foo bar")
ok := td.Cmp(t, got, td.Re(expected), "checks value %s", got)
fmt.Println(ok)
true
Example (CompiledStringer)

Code:

t := &testing.T{}

expected := regexp.MustCompile("(zip|bar)$")

got := bytes.NewBufferString("foo bar")
ok := td.Cmp(t, got, td.Re(expected), "checks value %s", got)
fmt.Println(ok)
true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foo bar")
ok := td.Cmp(t, got, td.Re("(zip|bar)$"), "checks value %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foo bar")
ok := td.Cmp(t, got, td.Re("(zip|bar)$"), "checks value %s", got)
fmt.Println(ok)
true

func ReAll

func ReAll(reg interface{}, capture interface{}) TestDeep

ReAll operator allows to successively apply a regexp on a string (or convertible), []byte, error or fmt.Stringer interface (error interface is tested before fmt.Stringer) and to match its groups contents.

"reg" is the regexp. It can be a string that is automatically compiled using regexp.MustCompile, or a *regexp.Regexp.

"capture" is used to match the contents of regexp groups. Groups are presented as a []string or [][]byte depending the original matched data. Note that an other operator can be used here.

td.Cmp(t, "John Doe",
  td.ReAll(`(\w+)(?: |\z)`, []string{"John", "Doe"})) // succeeds
td.Cmp(t, "John Doe",
  td.ReAll(`(\w+)(?: |\z)`, td.Bag("Doe", "John"))) // succeeds
Example (Capture)

Code:

t := &testing.T{}

got := "foo bar biz"
ok := td.Cmp(t, got, td.ReAll(`(\w+)`, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)

got = "foo BAR biz"
ok = td.Cmp(t, got, td.ReAll(`(\w+)`, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CaptureComplex)

Code:

t := &testing.T{}

got := "11 45 23 56 85 96"
ok := td.Cmp(t, got,
	td.ReAll(`(\d+)`, td.ArrayEach(td.Code(func(num string) bool {
		n, err := strconv.Atoi(num)
		return err == nil && n > 10 && n < 100
	}))),
	"checks value %s", got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.ReAll(`(\d+)`, td.ArrayEach(td.Code(func(num string) bool {
		n, err := strconv.Atoi(num)
		return err == nil && n > 20 && n < 100
	}))),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCapture)

Code:

t := &testing.T{}

expected := regexp.MustCompile(`(\w+)`)

got := "foo bar biz"
ok := td.Cmp(t, got, td.ReAll(expected, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)

got = "foo BAR biz"
ok = td.Cmp(t, got, td.ReAll(expected, td.Set("biz", "foo", "bar")),
	"checks value %s", got)
fmt.Println(ok)
true
false
Example (CompiledCaptureComplex)

Code:

t := &testing.T{}

expected := regexp.MustCompile(`(\d+)`)

got := "11 45 23 56 85 96"
ok := td.Cmp(t, got,
	td.ReAll(expected, td.ArrayEach(td.Code(func(num string) bool {
		n, err := strconv.Atoi(num)
		return err == nil && n > 10 && n < 100
	}))),
	"checks value %s", got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.ReAll(expected, td.ArrayEach(td.Code(func(num string) bool {
		n, err := strconv.Atoi(num)
		return err == nil && n > 20 && n < 100
	}))),
	"checks value %s", got)
fmt.Println(ok)
true
false

func SStruct

func SStruct(model interface{}, expectedFields StructFields) TestDeep

SStruct operator (aka strict-Struct) compares the contents of a struct or a pointer on a struct against values of "model" (if any) and the values of "expectedFields". The zero values are compared too even if they are omitted from "expectedFields": that is the difference with Struct operator.

"model" must be the same type as compared data.

"expectedFields" can be nil, if no TestDeep operators are involved.

To ignore a field, one has to specify it in "expectedFields" and use the Ignore operator.

td.Cmp(t, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "Age":      td.Between(40, 45),
    "Children": td.Ignore(),
  }),
)

During a match, all expected and zero fields must be found to succeed.

TypeBehind method returns the reflect.Type of "model".

Example

Code:

t := &testing.T{}

type Person struct {
	Name        string
	Age         int
	NumChildren int
}

got := Person{
	Name:        "Foobar",
	Age:         42,
	NumChildren: 0,
}

ok := td.Cmp(t, got,
	td.SStruct(Person{Name: "Foobar"}, td.StructFields{
		"Age": td.Between(40, 50),
	}),
	"checks %v is the right Person")
fmt.Println(ok)

got.NumChildren = 3
ok = td.Cmp(t, got,
	td.SStruct(Person{}, td.StructFields{
		"Name":        "Foobar",
		"Age":         td.Between(40, 50),
		"NumChildren": td.Not(0),
	}),
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.SStruct(&Person{}, td.StructFields{
		"Name":        "Foobar",
		"Age":         td.Between(40, 50),
		"NumChildren": td.Not(0),
	}),
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.SStruct((*Person)(nil), td.StructFields{
		"Name":        "Foobar",
		"Age":         td.Between(40, 50),
		"NumChildren": td.Not(0),
	}),
	"checks %v is the right Person")
fmt.Println(ok)
true
true
true
true

func Set

func Set(expectedItems ...interface{}) TestDeep

Set operator compares the contents of an array or a slice (or a pointer on array/slice) ignoring duplicates and without taking care of the order of items.

During a match, each expected item should match in the compared array/slice, and each array/slice item should be matched by an expected item to succeed.

td.Cmp(t, []int{1, 1, 2}, td.Set(1, 2))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.Set(2, 1))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.Set(1, 2, 3)) // fails, 3 is missing

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.Set(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

expected := []int{2, 1}
td.Cmp(t, []int{1, 1, 2}, td.Set(td.Flatten(expected))) // succeeds
// = td.Cmp(t, []int{1, 1, 2}, td.Set(2, 1))

exp1 := []int{2, 1}
exp2 := []int{5, 8}
td.Cmp(t, []int{1, 5, 1, 2, 8, 3, 3},
  td.Set(td.Flatten(exp1), 3, td.Flatten(exp2))) // succeeds
// = td.Cmp(t, []int{1, 5, 1, 2, 8, 3, 3}, td.Set(2, 1, 3, 5, 8))
Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.Cmp(t, got, td.Set(1, 2, 3, 5, 8),
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.Cmp(t, got, td.Set(1, 2, 2, 2, 2, 2, 3, 5, 8),
	"checks all items are present, in any order")
fmt.Println(ok)

ok = td.Cmp(t, got, td.Set(td.Between(1, 4), 3, td.Between(2, 10)),
	"checks all items are present, in any order")
fmt.Println(ok)

expected := []int{1, 2, 3, 5, 8}
ok = td.Cmp(t, got, td.Set(td.Flatten(expected)),
	"checks all expected items are present, in any order")
fmt.Println(ok)
true
true
true
true

func Shallow

func Shallow(expectedPtr interface{}) TestDeep

Shallow operator compares pointers only, not their contents. It applies on channels, functions (with some restrictions), maps, pointers, slices and strings.

During a match, the compared data must be the same as "expectedPtr" to succeed.

a, b := 123, 123
td.Cmp(t, &a, td.Shallow(&a)) // succeeds
td.Cmp(t, &a, td.Shallow(&b)) // fails even if a == b as &a != &b

back := "foobarfoobar"
a, b := back[:6], back[6:]
// a == b but...
td.Cmp(t, &a, td.Shallow(&b)) // fails

Be careful for slices and strings! Shallow can succeed but the slices/strings not be identical because of their different lengths. For example:

a := "foobar yes!"
b := a[:1]                    // aka "f"
td.Cmp(t, &a, td.Shallow(&b)) // succeeds as both strings point to the same area, even if len() differ

The same behavior occurs for slices:

a := []int{1, 2, 3, 4, 5, 6}
b := a[:2]                    // aka []int{1, 2}
td.Cmp(t, &a, td.Shallow(&b)) // succeeds as both slices point to the same area, even if len() differ
Example

Code:

t := &testing.T{}

type MyStruct struct {
	Value int
}
data := MyStruct{Value: 12}
got := &data

ok := td.Cmp(t, got, td.Shallow(&data),
	"checks pointers only, not contents")
fmt.Println(ok)

ok = td.Cmp(t, got, td.Shallow(&MyStruct{Value: 12}),
	"checks pointers only, not contents")
fmt.Println(ok)
true
false
Example (Slice)

Code:

t := &testing.T{}

back := []int{1, 2, 3, 1, 2, 3}
a := back[:3]
b := back[3:]

ok := td.Cmp(t, a, td.Shallow(back))
fmt.Println("are ≠ but share the same area:", ok)

ok = td.Cmp(t, b, td.Shallow(back))
fmt.Println("are = but do not point to same area:", ok)
are ≠ but share the same area: true
are = but do not point to same area: false
Example (String)

Code:

t := &testing.T{}

back := "foobarfoobar"
a := back[:6]
b := back[6:]

ok := td.Cmp(t, a, td.Shallow(back))
fmt.Println("are ≠ but share the same area:", ok)

ok = td.Cmp(t, b, td.Shallow(a))
fmt.Println("are = but do not point to same area:", ok)
are ≠ but share the same area: true
are = but do not point to same area: false

func Slice

func Slice(model interface{}, expectedEntries ArrayEntries) TestDeep

Slice operator compares the contents of a slice or a pointer on a slice against the non-zero values of "model" (if any) and the values of "expectedEntries".

"model" must be the same type as compared data.

"expectedEntries" can be nil, if no zero entries are expected and no TestDeep operator are involved.

got := []int{12, 14, 17}
td.Cmp(t, got, td.Slice([]int{0, 14}, td.ArrayEntries{0: 12, 2: 17})) // succeeds
td.Cmp(t, got,
  td.Slice([]int{0, 14}, td.ArrayEntries{0: td.Gt(10), 2: td.Gt(15)})) // succeeds

TypeBehind method returns the reflect.Type of "model".

Example (Slice)

Code:

t := &testing.T{}

got := []int{42, 58, 26}

ok := td.Cmp(t, got, td.Slice([]int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
	"checks slice %v", got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Slice([]int{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
	"checks slice %v", got)
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Slice(([]int)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
	"checks slice %v", got)
fmt.Println(ok)
true
true
true
Example (TypedSlice)

Code:

t := &testing.T{}

type MySlice []int

got := MySlice{42, 58, 26}

ok := td.Cmp(t, got, td.Slice(MySlice{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
	"checks typed slice %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got, td.Slice(&MySlice{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
	"checks pointer on typed slice %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Slice(&MySlice{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
	"checks pointer on typed slice %v", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Slice((*MySlice)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
	"checks pointer on typed slice %v", got)
fmt.Println(ok)
true
true
true
true

func Smuggle

func Smuggle(fn interface{}, expectedValue interface{}) TestDeep

Smuggle operator allows to change data contents or mutate it into another type before stepping down in favor of generic comparison process. Of course it is a smuggler operator. So "fn" is a function that must take one parameter whose type must be convertible to the type of the compared value (as a convenient shortcut, "fn" can be a string specifying a fields-path through structs, see below for details).

"fn" must return at least one value. These value will be compared as is to "expectedValue", here integer 28:

td.Cmp(t, "0028",
  td.Smuggle(func(value string) int {
    num, _ := strconv.Atoi(value)
    return num
  }, 28),
)

or using an other TestDeep operator, here Between(28, 30):

td.Cmp(t, "0029",
  td.Smuggle(func(value string) int {
    num, _ := strconv.Atoi(value)
    return num
  }, td.Between(28, 30)),
)

"fn" can return a second boolean value, used to tell that a problem occurred and so stop the comparison:

td.Cmp(t, "0029",
  td.Smuggle(func(value string) (int, bool) {
    num, err := strconv.Atoi(value)
    return num, err == nil
  }, td.Between(28, 30)),
)

"fn" can return a third string value which is used to describe the test when a problem occurred (false second boolean value):

td.Cmp(t, "0029",
  td.Smuggle(func(value string) (int, bool, string) {
    num, err := strconv.Atoi(value)
    if err != nil {
      return 0, false, "string must contain a number"
    }
    return num, true, ""
  }, td.Between(28, 30)),
)

Instead of returning (X, bool) or (X, bool, string), "fn" can return (X, error). When a problem occurs, the returned error is non-nil, as in:

td.Cmp(t, "0029",
  td.Smuggle(func(value string) (int, error) {
    num, err := strconv.Atoi(value)
    return num, err
  }, td.Between(28, 30)),
)

Which can be simplified to:

td.Cmp(t, "0029", td.Smuggle(strconv.Atoi, td.Between(28, 30)))

Imagine you want to compare that the Year of a date is between 2010 and 2020:

td.Cmp(t, time.Date(2015, time.May, 1, 1, 2, 3, 0, time.UTC),
  td.Smuggle(func(date time.Time) int { return date.Year() },
    td.Between(2010, 2020)),
)

In this case the data location forwarded to next test will be something like "DATA.MyTimeField<smuggled>", but you can act on it too by returning a SmuggledGot struct (by value or by address):

td.Cmp(t, time.Date(2015, time.May, 1, 1, 2, 3, 0, time.UTC),
  td.Smuggle(func(date time.Time) SmuggledGot {
    return SmuggledGot{
      Name: "Year",
      Got:  date.Year(),
    }
  }, td.Between(2010, 2020)),
)

then the data location forwarded to next test will be something like "DATA.MyTimeField.Year". The "." between the current path (here "DATA.MyTimeField") and the returned Name "Year" is automatically added when Name starts with a Letter.

Note that SmuggledGot and *SmuggledGot returns are treated equally, and they are only used when "fn" has only one returned value or when the second boolean returned value is true.

Of course, all cases can go together:

// Accepts a "YYYY/mm/DD HH:MM:SS" string to produce a time.Time and tests
// whether this date is contained between 2 hours before now and now.
td.Cmp(t, "2020-01-25 12:13:14",
  td.Smuggle(func(date string) (*SmuggledGot, bool, string) {
    date, err := time.Parse("2006/01/02 15:04:05", date)
    if err != nil {
      return nil, false, `date must conform to "YYYY/mm/DD HH:MM:SS" format`
    }
    return &SmuggledGot{
      Name: "Date",
      Got:  date,
    }, true, ""
  }, td.Between(time.Now().Add(-2*time.Hour), time.Now())),
)

or:

// Accepts a "YYYY/mm/DD HH:MM:SS" string to produce a time.Time and tests
// whether this date is contained between 2 hours before now and now.
td.Cmp(t, "2020-01-25 12:13:14",
  td.Smuggle(func(date string) (*SmuggledGot, error) {
    date, err := time.Parse("2006/01/02 15:04:05", date)
    if err != nil {
      return nil, err
    }
    return &SmuggledGot{
      Name: "Date",
      Got:  date,
    }, nil
  }, td.Between(time.Now().Add(-2*time.Hour), time.Now())),
)

Smuggle can also be used to access a struct field embedded in several struct layers.

type A struct{ Num int }
type B struct{ A *A }
type C struct{ B B }
got := C{B: B{A: &A{Num: 12}}}

// Tests that got.B.A.Num is 12
td.Cmp(t, got,
  td.Smuggle(func(c C) int {
    return c.B.A.Num
  }, 12))

As brought up above, a field-path can be passed as "fn" value instead of a function pointer. Using this feature, the Cmp call in the above example can be rewritten as follows:

// Tests that got.B.A.Num is 12
td.Cmp(t, got, td.Smuggle("B.A.Num", 12))

Behind the scenes, a temporary function is automatically created to achieve the same goal, but add some checks against nil values and auto-dereference interfaces and pointers.

The difference between Smuggle and Code operators is that Code is used to do a final comparison while Smuggle transforms the data and then steps down in favor of generic comparison process. Moreover, the type accepted as input for the function is more lax to facilitate the tests writing (e.g. the function can accept a float64 and the got value be an int). See examples. On the other hand, the output type is strict and must match exactly the expected value type. The fields-path string "fn" shortcut is not available with Code operator.

TypeBehind method returns the reflect.Type of only parameter of "fn". For the case where "fn" is a fields-path, it is always interface{}, as the type can not be known in advance.

Example (Auto_unmarshal)

Code:

t := &testing.T{}

got := []byte(`{"a":1,"b":2}`)

ok := td.Cmp(t, got,
	td.Smuggle(
		func(b json.RawMessage) (r map[string]int, err error) {
			err = json.Unmarshal(b, &r)
			return
		},
		map[string]int{
			"a": 1,
			"b": 2,
		}))
fmt.Println("JSON contents is OK:", ok)
JSON contents is OK: true
Example (Complex)

Code:

t := &testing.T{}

// No end date but a start date and a duration
type StartDuration struct {
	StartDate time.Time
	Duration  time.Duration
}

for _, duration := range []time.Duration{48, 72, 96} {
	got := StartDuration{
		StartDate: time.Date(2018, time.February, 14, 12, 13, 14, 0, time.UTC),
		Duration:  duration * time.Hour,
	}

	ok := td.Cmp(t, got,
		td.Smuggle(
			func(sd StartDuration) time.Time {
				return sd.StartDate.Add(sd.Duration)
			},
			td.Between(
				time.Date(2018, time.February, 17, 0, 0, 0, 0, time.UTC),
				time.Date(2018, time.February, 19, 0, 0, 0, 0, time.UTC))))
	fmt.Println(ok)

	ok = td.Cmp(t, got,
		td.Smuggle(
			func(sd StartDuration) td.SmuggledGot {
				return td.SmuggledGot{
					Name: "ComputedEndDate",
					Got:  sd.StartDate.Add(sd.Duration),
				}
			},
			td.Between(
				time.Date(2018, time.February, 17, 0, 0, 0, 0, time.UTC),
				time.Date(2018, time.February, 19, 0, 0, 0, 0, time.UTC))))
	fmt.Println(ok)
}
false
false
true
true
true
true
Example (Convert)

Code:

t := &testing.T{}

got := int64(123)

ok := td.Cmp(t, got,
	td.Smuggle(func(n int64) int { return int(n) }, 123),
	"checks int64 got against an int value")
fmt.Println(ok)

ok = td.Cmp(t, "123",
	td.Smuggle(
		func(numStr string) (int, bool) {
			n, err := strconv.Atoi(numStr)
			return n, err == nil
		},
		td.Between(120, 130)),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = td.Cmp(t, "123",
	td.Smuggle(
		func(numStr string) (int, bool, string) {
			n, err := strconv.Atoi(numStr)
			if err != nil {
				return 0, false, "string must contain a number"
			}
			return n, true, ""
		},
		td.Between(120, 130)),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = td.Cmp(t, "123",
	td.Smuggle(
		func(numStr string) (int, error) {
			return strconv.Atoi(numStr)
		},
		td.Between(120, 130)),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)

ok = td.Cmp(t, "123",
	td.Smuggle(strconv.Atoi, td.Between(120, 130)),
	"checks that number in %#v is in [120 .. 130]")
fmt.Println(ok)
true
true
true
true
true
Example (Field_path)

Code:

t := &testing.T{}

type Body struct {
	Name  string
	Value interface{}
}
type Request struct {
	Body *Body
}
type Transaction struct {
	Request
}
type ValueNum struct {
	Num int
}

got := &Transaction{
	Request: Request{
		Body: &Body{
			Name:  "test",
			Value: &ValueNum{Num: 123},
		},
	},
}

ok := td.Cmp(t, got,
	td.Smuggle(
		func(t *Transaction) (int, error) {
			if t.Request.Body == nil ||
				t.Request.Body.Value == nil {
				return 0, errors.New("Request.Body or Request.Body.Value is nil")
			}
			if v, ok := t.Request.Body.Value.(*ValueNum); ok && v != nil {
				return v.Num, nil
			}
			return 0, errors.New("Request.Body.Value isn't *ValueNum or nil")
		},
		td.Between(100, 200)))
fmt.Println("check Num by hand:", ok)

ok = td.Cmp(t, got, td.Smuggle("Request.Body.Value.Num", td.Between(100, 200)))
fmt.Println("check Num using a fields-path:", ok)

ok = td.Cmp(t, got, td.Smuggle("Body.Value.Num", td.Between(100, 200)))
fmt.Println("check Num using an other fields-path:", ok)
check Num by hand: true
check Num using a fields-path: true
check Num using an other fields-path: true
Example (Interface)

Code:

t := &testing.T{}

gotTime, err := time.Parse(time.RFC3339, "2018-05-23T12:13:14Z")
if err != nil {
	t.Fatal(err)
}

ok := td.Cmp(t, gotTime,
	td.Smuggle(func(s fmt.Stringer) string {
		return s.String()
	},
		"2018-05-23 12:13:14 +0000 UTC"))
fmt.Println("stringified time.Time OK:", ok)

// If got does not implement the fmt.Stringer interface, it fails
// without calling the Smuggle func
type MyTime time.Time
ok = td.Cmp(t, MyTime(gotTime),
	td.Smuggle(func(s fmt.Stringer) string {
		fmt.Println("Smuggle func called!")
		return s.String()
	},
		"2018-05-23 12:13:14 +0000 UTC"))
fmt.Println("stringified MyTime OK:", ok)
Example (Lax)

Code:

t := &testing.T{}

got := int(123)

ok := td.Cmp(t, got,
	td.Smuggle(func(n int64) uint32 { return uint32(n) }, uint32(123)))
fmt.Println("got int16(123) → smuggle via int64 → uint32(123):", ok)
got int16(123) → smuggle via int64 → uint32(123): true

func String

func String(expected string) TestDeep

String operator allows to compare a string (or convertible), []byte (or convertible), error or fmt.Stringer interface (error interface is tested before fmt.Stringer).

err := errors.New("error!")
td.Cmp(t, err, td.String("error!")) // succeeds

bstr := bytes.NewBufferString("fmt.Stringer!")
td.Cmp(t, bstr, td.String("fmt.Stringer!")) // succeeds
Example

Code:

t := &testing.T{}

got := "foobar"

ok := td.Cmp(t, got, td.String("foobar"), "checks %s", got)
fmt.Println("using string:", ok)

ok = td.Cmp(t, []byte(got), td.String("foobar"), "checks %s", got)
fmt.Println("using []byte:", ok)
using string: true
using []byte: true
Example (Error)

Code:

t := &testing.T{}

got := errors.New("foobar")

ok := td.Cmp(t, got, td.String("foobar"), "checks %s", got)
fmt.Println(ok)
true
Example (Stringer)

Code:

t := &testing.T{}

got := bytes.NewBufferString("foobar")

ok := td.Cmp(t, got, td.String("foobar"), "checks %s", got)
fmt.Println(ok)
true

func Struct

func Struct(model interface{}, expectedFields StructFields) TestDeep

Struct operator compares the contents of a struct or a pointer on a struct against the non-zero values of "model" (if any) and the values of "expectedFields". See SStruct to compares against zero fields without specifying them in "expectedFields".

"model" must be the same type as compared data.

"expectedFields" can be nil, if no zero entries are expected and no TestDeep operators are involved.

td.Cmp(t, td.Struct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "Age":      td.Between(40, 45),
    "Children": 0,
  }),
)

During a match, all expected fields must be found to succeed. Non-expected fields are ignored.

TypeBehind method returns the reflect.Type of "model".

Example

Code:

t := &testing.T{}

type Person struct {
	Name        string
	Age         int
	NumChildren int
}

got := Person{
	Name:        "Foobar",
	Age:         42,
	NumChildren: 3,
}

ok := td.Cmp(t, got,
	td.Struct(Person{Name: "Foobar"}, td.StructFields{
		"Age": td.Between(40, 50),
	}),
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.Cmp(t, got,
	td.Struct(Person{}, td.StructFields{
		"Name":        "Foobar",
		"Age":         td.Between(40, 50),
		"NumChildren": td.Not(0),
	}),
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Struct(&Person{}, td.StructFields{
		"Name":        "Foobar",
		"Age":         td.Between(40, 50),
		"NumChildren": td.Not(0),
	}),
	"checks %v is the right Person")
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.Struct((*Person)(nil), td.StructFields{
		"Name":        "Foobar",
		"Age":         td.Between(40, 50),
		"NumChildren": td.Not(0),
	}),
	"checks %v is the right Person")
fmt.Println(ok)
true
true
true
true

func SubBagOf

func SubBagOf(expectedItems ...interface{}) TestDeep

SubBagOf operator compares the contents of an array or a slice (or a pointer on array/slice) without taking care of the order of items.

During a match, each array/slice item should be matched by an expected item to succeed. But some expected items can be missing from the compared array/slice.

td.Cmp(t, []int{1}, td.SubBagOf(1, 1, 2))       // succeeds
td.Cmp(t, []int{1, 1, 1}, td.SubBagOf(1, 1, 2)) // fails, one 1 is an extra item

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.SubBagOf(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

expected := []int{1, 2, 1}
td.Cmp(t, []int{1}, td.SubBagOf(td.Flatten(expected))) // succeeds
// = td.Cmp(t, []int{1}, td.SubBagOf(1, 2, 1))

exp1 := []int{5, 1, 1}
exp2 := []int{8, 42, 3}
td.Cmp(t, []int{1, 42, 3},
  td.SubBagOf(td.Flatten(exp1), 3, td.Flatten(exp2))) // succeeds
// = td.Cmp(t, []int{1, 42, 3}, td.SubBagOf(5, 1, 1, 3, 8, 42, 3))
Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.Cmp(t, got, td.SubBagOf(0, 0, 1, 1, 2, 2, 3, 3, 5, 5, 8, 8, 9, 9),
	"checks at least all items are present, in any order")
fmt.Println(ok)

ok = td.Cmp(t, got, td.SubBagOf(0, 0, 1, 1, 2, 2, 3, 3, 5, 5, 8, 9, 9),
	"checks at least all items are present, in any order")
fmt.Println(ok)

got = []int{1, 3, 5, 2}

ok = td.Cmp(t, got, td.SubBagOf(
	td.Between(0, 3),
	td.Between(0, 3),
	td.Between(0, 3),
	td.Between(0, 3),
	td.Gt(4),
	td.Gt(4)),
	"checks at least all items match, in any order with TestDeep operators")
fmt.Println(ok)

expected := []int{1, 2, 3, 5, 9, 8}
ok = td.Cmp(t, got, td.SubBagOf(td.Flatten(expected)),
	"checks at least all expected items are present, in any order")
fmt.Println(ok)
true
false
true
true

func SubJSONOf

func SubJSONOf(expectedJSON interface{}, params ...interface{}) TestDeep

SubJSONOf operator allows to compare the JSON representation of data against "expectedJSON". Unlike JSON operator, marshalled data must be a JSON object/map (aka {…}). "expectedJSON" can be a:

- string containing JSON data like `{"fullname":"Bob","age":42}`
- string containing a JSON filename, ending with ".json" (its
  content is ioutil.ReadFile before unmarshaling)
- []byte containing JSON data
- io.Reader stream containing JSON data (is ioutil.ReadAll before
  unmarshaling)

JSON data contained in "expectedJSON" must be a JSON object/map (aka {…}) too. During a match, each expected entry should match in the compared map. But some expected entries can be missing from the compared map.

type MyStruct struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
}
got := MyStruct{
  Name: "Bob",
  Age:  42,
}
td.Cmp(t, got, td.SubJSONOf(`{"name": "Bob", "age": 42, "city": "NY"}`)) // succeeds
td.Cmp(t, got, td.SubJSONOf(`{"name": "Bob", "zip": 666}`))              // fails, extra "age"

"expectedJSON" JSON value can contain placeholders. The "params" are for any placeholder parameters in "expectedJSON". "params" can contain TestDeep operators as well as raw values. A placeholder can be numeric like $2 or named like $name and always references an item in "params".

Numeric placeholders reference the n'th "operators" item (starting at 1). Named placeholders are used with Tag operator as follows:

td.Cmp(t, gotValue,
  td.SubJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

Note that placeholders can be double-quoted as in:

td.Cmp(t, gotValue,
  td.SubJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

It makes no difference whatever the underlying type of the replaced item is (= double quoting a placeholder matching a number is not a problem). It is just a matter of taste, double-quoting placeholders can be preferred when the JSON data has to conform to the JSON specification, like when used in a ".json" file.

Note "expectedJSON" can be a []byte, JSON filename or io.Reader:

td.Cmp(t, gotValue, td.SubJSONOf("file.json", td.Between(12, 34)))
td.Cmp(t, gotValue, td.SubJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))
td.Cmp(t, gotValue, td.SubJSONOf(osFile, td.Between(12, 34)))

A JSON filename ends with ".json".

To avoid a legit "$" string prefix causes a bad placeholder error, just double it to escape it. Note it is only needed when the "$" is the first character of a string:

td.Cmp(t, gotValue,
  td.SubJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

For the "details" key, the raw value "$info" is expected, no placeholders are involved here.

Note that Lax mode is automatically enabled by SubJSONOf operator to simplify numeric tests.

Comments can be embedded in JSON data:

td.Cmp(t, gotValue,
  SubJSONOf(`
{
  // A guy properties:
  "fullname": "$name",  // The full name of the guy
  "details":  "$$info", // Literally "$info", thanks to "$" escape
  "age":      $2        /* The age of the guy:
                           - placeholder unquoted, but could be without
                             any change
                           - to demonstrate a multi-lines comment */
}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

Comments, like in go, have 2 forms. To quote the Go language specification:

- line comments start with the character sequence // and stop at the
  end of the line.
- multi-lines comments start with the character sequence /* and stop
  with the first subsequent character sequence */.

Last but not least, simple operators can be directly embedded in JSON data without requiring any placeholder but using directly $^OperatorName. They are operator shortcuts:

td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $1}`, td.NotZero()))

can be written as:

td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero}`))

Unfortunately, only simple operators (in fact those which take no parameters) have shortcuts. They follow:

- Empty    → $^Empty
- Ignore   → $^Ignore
- NaN      → $^NaN
- Nil      → $^Nil
- NotEmpty → $^NotEmpty
- NotNaN   → $^NotNaN
- NotNil   → $^NotNil
- NotZero  → $^NotZero
- Zero     → $^Zero

TypeBehind method returns the map[string]interface{} type.

Example (Basic)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob",
	Age:      42,
}

ok := td.Cmp(t, got, td.SubJSONOf(`{"age":42,"fullname":"Bob","gender":"male"}`))
fmt.Println("check got with age then fullname:", ok)

ok = td.Cmp(t, got, td.SubJSONOf(`{"fullname":"Bob","age":42,"gender":"male"}`))
fmt.Println("check got with fullname then age:", ok)

ok = td.Cmp(t, got, td.SubJSONOf(`
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42,    /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
  "gender":   "male" // This field is ignored as SubJSONOf
}`))
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = td.Cmp(t, got, td.SubJSONOf(`{"fullname":"Bob","gender":"male"}`))
fmt.Println("check got without age field:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got without age field: false
Example (File)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender",
  "details":  {
    "city": "TestCity",
    "zip":  666
  }
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := td.Cmp(t, got,
	td.SubJSONOf(filename,
		td.Tag("name", td.HasPrefix("Bob")),
		td.Tag("age", td.Between(40, 45)),
		td.Tag("gender", td.Re(`^(male|female)\z`))))
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = td.Cmp(t, got,
	td.SubJSONOf(file,
		td.Tag("name", td.HasPrefix("Bob")),
		td.Tag("age", td.Between(40, 45)),
		td.Tag("gender", td.Re(`^(male|female)\z`))))
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
}

ok := td.Cmp(t, got,
	td.SubJSONOf(`{"age": $1, "fullname": $2, "gender": $3}`,
		42, "Bob Foobar", "male"))
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = td.Cmp(t, got,
	td.SubJSONOf(`{"age": $1, "fullname": $2, "gender": $3}`,
		td.Between(40, 45),
		td.HasSuffix("Foobar"),
		td.NotEmpty()))
fmt.Println("check got with numeric placeholders:", ok)

ok = td.Cmp(t, got,
	td.SubJSONOf(`{"age": "$1", "fullname": "$2", "gender": "$3"}`,
		td.Between(40, 45),
		td.HasSuffix("Foobar"),
		td.NotEmpty()))
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = td.Cmp(t, got,
	td.SubJSONOf(`{"age": $age, "fullname": $name, "gender": $gender}`,
		td.Tag("age", td.Between(40, 45)),
		td.Tag("name", td.HasSuffix("Foobar")),
		td.Tag("gender", td.NotEmpty())))
fmt.Println("check got with named placeholders:", ok)

ok = td.Cmp(t, got,
	td.SubJSONOf(`{"age": $^NotZero, "fullname": $^NotEmpty, "gender": $^NotEmpty}`))
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func SubMapOf

func SubMapOf(model interface{}, expectedEntries MapEntries) TestDeep

SubMapOf operator compares the contents of a map against the non-zero values of "model" (if any) and the values of "expectedEntries".

"model" must be the same type as compared data.

"expectedEntries" can be nil, if no zero entries are expected and no TestDeep operator are involved.

During a match, each map entry should be matched by an expected entry to succeed. But some expected entries can be missing from the compared map.

got := map[string]string{
  "foo": "test",
  "zip": "buzz",
}
td.Cmp(t, got, td.SubMapOf(
  map[string]string{
    "foo": "test",
    "bar": "wizz",
  },
  td.MapEntries{
    "zip": td.HasSuffix("zz"),
  }),
) // succeeds

td.Cmp(t, got, td.SubMapOf(
  map[string]string{
    "bar": "wizz",
  },
  td.MapEntries{
    "zip": td.HasSuffix("zz"),
  }),
) // fails, extra {"foo": "test"} in got

TypeBehind method returns the reflect.Type of "model".

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42}

ok := td.Cmp(t, got,
	td.SubMapOf(map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666}),
	"checks map %v is included in expected keys/values", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42}

ok := td.Cmp(t, got,
	td.SubMapOf(MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666}),
	"checks typed map %v is included in expected keys/values", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.SubMapOf(&MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15), "zip": 666}),
	"checks pointed typed map %v is included in expected keys/values", got)
fmt.Println(ok)
true
true

func SubSetOf

func SubSetOf(expectedItems ...interface{}) TestDeep

SubSetOf operator compares the contents of an array or a slice (or a pointer on array/slice) ignoring duplicates and without taking care of the order of items.

During a match, each array/slice item should be matched by an expected item to succeed. But some expected items can be missing from the compared array/slice.

td.Cmp(t, []int{1, 1}, td.SubSetOf(1, 2))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.SubSetOf(1, 3)) // fails, 2 is an extra item

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.SubSetOf(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

expected := []int{2, 1}
td.Cmp(t, []int{1, 1}, td.SubSetOf(td.Flatten(expected))) // succeeds
// = td.Cmp(t, []int{1, 1}, td.SubSetOf(2, 1))

exp1 := []int{2, 1}
exp2 := []int{5, 8}
td.Cmp(t, []int{1, 5, 1, 3, 3},
  td.SubSetOf(td.Flatten(exp1), 3, td.Flatten(exp2))) // succeeds
// = td.Cmp(t, []int{1, 5, 1, 3, 3}, td.SubSetOf(2, 1, 3, 5, 8))
Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.Cmp(t, got, td.SubSetOf(1, 2, 3, 4, 5, 6, 7, 8),
	"checks at least all items are present, in any order, ignoring duplicates")
fmt.Println(ok)

ok = td.Cmp(t, got, td.SubSetOf(td.Between(1, 4), 3, td.Between(2, 10), td.Gt(100)),
	"checks at least all items are present, in any order, ignoring duplicates")
fmt.Println(ok)

expected := []int{1, 2, 3, 4, 5, 6, 7, 8}
ok = td.Cmp(t, got, td.SubSetOf(td.Flatten(expected)),
	"checks at least all expected items are present, in any order, ignoring duplicates")
fmt.Println(ok)
true
true
true

func SuperBagOf

func SuperBagOf(expectedItems ...interface{}) TestDeep

SuperBagOf operator compares the contents of an array or a slice (or a pointer on array/slice) without taking care of the order of items.

During a match, each expected item should match in the compared array/slice. But some items in the compared array/slice may not be expected.

td.Cmp(t, []int{1, 1, 2}, td.SuperBagOf(1))       // succeeds
td.Cmp(t, []int{1, 1, 2}, td.SuperBagOf(1, 1, 1)) // fails, one 1 is missing

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.SuperBagOf(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

expected := []int{1, 2, 1}
td.Cmp(t, []int{1}, td.SuperBagOf(td.Flatten(expected))) // succeeds
// = td.Cmp(t, []int{1}, td.SuperBagOf(1, 2, 1))

exp1 := []int{5, 1, 1}
exp2 := []int{8, 42}
td.Cmp(t, []int{1, 5, 1, 8, 42, 3, 3, 6},
  td.SuperBagOf(td.Flatten(exp1), 3, td.Flatten(exp2))) // succeeds
// = td.Cmp(t, []int{1, 5, 1, 8, 42, 3, 3, 6}, td.SuperBagOf(5, 1, 1, 3, 8, 42))
Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.Cmp(t, got, td.SuperBagOf(8, 5, 8),
	"checks the items are present, in any order")
fmt.Println(ok)

ok = td.Cmp(t, got, td.SuperBagOf(td.Gt(5), td.Lte(2)),
	"checks at least 2 items of %v match", got)
fmt.Println(ok)

expected := []int{8, 5, 8}
ok = td.Cmp(t, got, td.SuperBagOf(td.Flatten(expected)),
	"checks the expected items are present, in any order")
fmt.Println(ok)
true
true
true

func SuperJSONOf

func SuperJSONOf(expectedJSON interface{}, params ...interface{}) TestDeep

SuperJSONOf operator allows to compare the JSON representation of data against "expectedJSON". Unlike JSON operator, marshalled data must be a JSON object/map (aka {…}). "expectedJSON" can be a:

- string containing JSON data like `{"fullname":"Bob","age":42}`
- string containing a JSON filename, ending with ".json" (its
  content is ioutil.ReadFile before unmarshaling)
- []byte containing JSON data
- io.Reader stream containing JSON data (is ioutil.ReadAll before
  unmarshaling)

JSON data contained in "expectedJSON" must be a JSON object/map (aka {…}) too. During a match, each expected entry should match in the compared map. But some entries in the compared map may not be expected.

type MyStruct struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
  City string `json:"city"`
}
got := MyStruct{
  Name: "Bob",
  Age:  42,
  City: "TestCity",
}
td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "age": 42}`))  // succeeds
td.Cmp(t, got, td.SuperJSONOf(`{"name": "Bob", "zip": 666}`)) // fails, miss "zip"

"expectedJSON" JSON value can contain placeholders. The "params" are for any placeholder parameters in "expectedJSON". "params" can contain TestDeep operators as well as raw values. A placeholder can be numeric like $2 or named like $name and always references an item in "params".

Numeric placeholders reference the n'th "operators" item (starting at 1). Named placeholders are used with Tag operator as follows:

td.Cmp(t, gotValue,
  SuperJSONOf(`{"fullname": $name, "age": $2, "gender": $3}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

Note that placeholders can be double-quoted as in:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"fullname": "$name", "age": "$2", "gender": "$3"}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43),                  // matches only $2
    "male"))                             // matches only $3

It makes no difference whatever the underlying type of the replaced item is (= double quoting a placeholder matching a number is not a problem). It is just a matter of taste, double-quoting placeholders can be preferred when the JSON data has to conform to the JSON specification, like when used in a ".json" file.

Note "expectedJSON" can be a []byte, JSON filename or io.Reader:

td.Cmp(t, gotValue, td.SuperJSONOf("file.json", td.Between(12, 34)))
td.Cmp(t, gotValue, td.SuperJSONOf([]byte(`[1, $1, 3]`), td.Between(12, 34)))
td.Cmp(t, gotValue, td.SuperJSONOf(osFile, td.Between(12, 34)))

A JSON filename ends with ".json".

To avoid a legit "$" string prefix causes a bad placeholder error, just double it to escape it. Note it is only needed when the "$" is the first character of a string:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"fullname": "$name", "details": "$$info", "age": $2}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

For the "details" key, the raw value "$info" is expected, no placeholders are involved here.

Note that Lax mode is automatically enabled by SuperJSONOf operator to simplify numeric tests.

Comments can be embedded in JSON data:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`
{
  // A guy properties:
  "fullname": "$name",  // The full name of the guy
  "details":  "$$info", // Literally "$info", thanks to "$" escape
  "age":      $2        /* The age of the guy:
                           - placeholder unquoted, but could be without
                             any change
                           - to demonstrate a multi-lines comment */
}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $1 and $name
    td.Between(41, 43)))                 // matches only $2

Comments, like in go, have 2 forms. To quote the Go language specification:

- line comments start with the character sequence // and stop at the
  end of the line.
- multi-lines comments start with the character sequence /* and stop
  with the first subsequent character sequence */.

Last but not least, simple operators can be directly embedded in JSON data without requiring any placeholder but using directly $^OperatorName. They are operator shortcuts:

td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $1}`, td.NotZero()))

can be written as:

td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero}`))

Unfortunately, only simple operators (in fact those which take no parameters) have shortcuts. They follow:

- Empty    → $^Empty
- Ignore   → $^Ignore
- NaN      → $^NaN
- Nil      → $^Nil
- NotEmpty → $^NotEmpty
- NotNaN   → $^NotNaN
- NotNil   → $^NotNil
- NotZero  → $^NotZero
- Zero     → $^Zero

TypeBehind method returns the map[string]interface{} type.

Example (Basic)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

ok := td.Cmp(t, got, td.SuperJSONOf(`{"age":42,"fullname":"Bob","gender":"male"}`))
fmt.Println("check got with age then fullname:", ok)

ok = td.Cmp(t, got, td.SuperJSONOf(`{"fullname":"Bob","age":42,"gender":"male"}`))
fmt.Println("check got with fullname then age:", ok)

ok = td.Cmp(t, got, td.SuperJSONOf(`
// This should be the JSON representation of a struct
{
  // A person:
  "fullname": "Bob", // The name of this person
  "age":      42,    /* The age of this person:
                        - 42 of course
                        - to demonstrate a multi-lines comment */
  "gender":   "male" // The gender!
}`))
fmt.Println("check got with nicely formatted and commented JSON:", ok)

ok = td.Cmp(t, got,
	td.SuperJSONOf(`{"fullname":"Bob","gender":"male","details":{}}`))
fmt.Println("check got with details field:", ok)
check got with age then fullname: true
check got with fullname then age: true
check got with nicely formatted and commented JSON: true
check got with details field: false
Example (File)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

tmpDir, err := ioutil.TempDir("", "")
if err != nil {
	t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

filename := tmpDir + "/test.json"
if err = ioutil.WriteFile(filename, []byte(`
{
  "fullname": "$name",
  "age":      "$age",
  "gender":   "$gender"
}`), 0644); err != nil {
	t.Fatal(err)
}

ok := td.Cmp(t, got,
	td.SuperJSONOf(filename,
		td.Tag("name", td.HasPrefix("Bob")),
		td.Tag("age", td.Between(40, 45)),
		td.Tag("gender", td.Re(`^(male|female)\z`))))
fmt.Println("Full match from file name:", ok)

file, err := os.Open(filename)
if err != nil {
	t.Fatal(err)
}
ok = td.Cmp(t, got,
	td.SuperJSONOf(file,
		td.Tag("name", td.HasPrefix("Bob")),
		td.Tag("age", td.Between(40, 45)),
		td.Tag("gender", td.Re(`^(male|female)\z`))))
fmt.Println("Full match from io.Reader:", ok)
Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)

Code:

t := &testing.T{}

got := &struct {
	Fullname string `json:"fullname"`
	Age      int    `json:"age"`
	Gender   string `json:"gender"`
	City     string `json:"city"`
	Zip      int    `json:"zip"`
}{
	Fullname: "Bob Foobar",
	Age:      42,
	Gender:   "male",
	City:     "TestCity",
	Zip:      666,
}

ok := td.Cmp(t, got,
	td.SuperJSONOf(`{"age": $1, "fullname": $2, "gender": $3}`,
		42, "Bob Foobar", "male"))
fmt.Println("check got with numeric placeholders without operators:", ok)

ok = td.Cmp(t, got,
	td.SuperJSONOf(`{"age": $1, "fullname": $2, "gender": $3}`,
		td.Between(40, 45),
		td.HasSuffix("Foobar"),
		td.NotEmpty()))
fmt.Println("check got with numeric placeholders:", ok)

ok = td.Cmp(t, got,
	td.SuperJSONOf(`{"age": "$1", "fullname": "$2", "gender": "$3"}`,
		td.Between(40, 45),
		td.HasSuffix("Foobar"),
		td.NotEmpty()))
fmt.Println("check got with double-quoted numeric placeholders:", ok)

ok = td.Cmp(t, got,
	td.SuperJSONOf(`{"age": $age, "fullname": $name, "gender": $gender}`,
		td.Tag("age", td.Between(40, 45)),
		td.Tag("name", td.HasSuffix("Foobar")),
		td.Tag("gender", td.NotEmpty())))
fmt.Println("check got with named placeholders:", ok)

ok = td.Cmp(t, got,
	td.SuperJSONOf(`{"age": $^NotZero, "fullname": $^NotEmpty, "gender": $^NotEmpty}`))
fmt.Println("check got with operator shortcuts:", ok)
check got with numeric placeholders without operators: true
check got with numeric placeholders: true
check got with double-quoted numeric placeholders: true
check got with named placeholders: true
check got with operator shortcuts: true

func SuperMapOf

func SuperMapOf(model interface{}, expectedEntries MapEntries) TestDeep

SuperMapOf operator compares the contents of a map against the non-zero values of "model" (if any) and the values of "expectedEntries".

"model" must be the same type as compared data.

"expectedEntries" can be nil, if no zero entries are expected and no TestDeep operator are involved.

During a match, each expected entry should match in the compared map. But some entries in the compared map may not be expected.

got := map[string]string{
  "foo": "test",
  "bar": "wizz",
  "zip": "buzz",
}
td.Cmp(t, got, td.SuperMapOf(
  map[string]string{
    "foo": "test",
  },
  td.MapEntries{
    "zip": td.HasSuffix("zz"),
  }),
) // succeeds

td.Cmp(t, got, td.SuperMapOf(
  map[string]string{
    "foo": "test",
  },
  td.MapEntries{
    "biz": td.HasSuffix("zz"),
  }),
) // fails, missing {"biz": …} in got

TypeBehind method returns the reflect.Type of "model".

Example (Map)

Code:

t := &testing.T{}

got := map[string]int{"foo": 12, "bar": 42, "zip": 89}

ok := td.Cmp(t, got,
	td.SuperMapOf(map[string]int{"bar": 42}, td.MapEntries{"foo": td.Lt(15)}),
	"checks map %v contains at leat all expected keys/values", got)
fmt.Println(ok)
true
Example (TypedMap)

Code:

t := &testing.T{}

type MyMap map[string]int

got := MyMap{"foo": 12, "bar": 42, "zip": 89}

ok := td.Cmp(t, got,
	td.SuperMapOf(MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15)}),
	"checks typed map %v contains at leat all expected keys/values", got)
fmt.Println(ok)

ok = td.Cmp(t, &got,
	td.SuperMapOf(&MyMap{"bar": 42}, td.MapEntries{"foo": td.Lt(15)}),
	"checks pointed typed map %v contains at leat all expected keys/values",
	got)
fmt.Println(ok)
true
true

func SuperSetOf

func SuperSetOf(expectedItems ...interface{}) TestDeep

SuperSetOf operator compares the contents of an array or a slice (or a pointer on array/slice) ignoring duplicates and without taking care of the order of items.

During a match, each expected item should match in the compared array/slice. But some items in the compared array/slice may not be expected.

td.Cmp(t, []int{1, 1, 2}, td.SuperSetOf(1))    // succeeds
td.Cmp(t, []int{1, 1, 2}, td.SuperSetOf(1, 3)) // fails, 3 is missing

// works with slices/arrays of any type
td.Cmp(t, personSlice, td.SuperSetOf(
  Person{Name: "Bob", Age: 32},
  Person{Name: "Alice", Age: 26},
))

To flatten a non-[]interface{} slice/array, use Flatten function and so avoid boring and inefficient copies:

expected := []int{2, 1}
td.Cmp(t, []int{1, 1, 2, 8}, td.SuperSetOf(td.Flatten(expected))) // succeeds
// = td.Cmp(t, []int{1, 1, 2, 8}, td.SubSetOf(2, 1))

exp1 := []int{2, 1}
exp2 := []int{5, 8}
td.Cmp(t, []int{1, 5, 1, 8, 42, 3, 3},
  td.SuperSetOf(td.Flatten(exp1), 3, td.Flatten(exp2))) // succeeds
// = td.Cmp(t, []int{1, 5, 1, 8, 42, 3, 3}, td.SuperSetOf(2, 1, 3, 5, 8))
Example

Code:

t := &testing.T{}

got := []int{1, 3, 5, 8, 8, 1, 2}

ok := td.Cmp(t, got, td.SuperSetOf(1, 2, 3),
	"checks the items are present, in any order and ignoring duplicates")
fmt.Println(ok)

ok = td.Cmp(t, got, td.SuperSetOf(td.Gt(5), td.Lte(2)),
	"checks at least 2 items of %v match ignoring duplicates", got)
fmt.Println(ok)

expected := []int{1, 2, 3}
ok = td.Cmp(t, got, td.SuperSetOf(td.Flatten(expected)),
	"checks the expected items are present, in any order and ignoring duplicates")
fmt.Println(ok)
true
true
true

func Tag

func Tag(tag string, expectedValue interface{}) TestDeep

Tag is a smuggler operator. It only allows to name "expectedValue", which can be an operator or a value. The data is then compared against "expectedValue" as if Tag was never called. It is only useful as JSON operator parameter, to name placeholders. See JSON operator for more details.

td.Cmp(t, gotValue,
  td.JSON(`{"fullname": $name, "age": $age, "gender": $gender}`,
    td.Tag("name", td.HasPrefix("Foo")), // matches $name
    td.Tag("age", td.Between(41, 43)),   // matches $age
    td.Tag("gender", "male")))           // matches $gender

TypeBehind method is delegated to "expectedValue" one if "expectedValue" is a TestDeep operator, otherwise it returns the type of "expectedValue" (or nil if it is originally untyped nil).

func TruncTime

func TruncTime(expectedTime interface{}, trunc ...time.Duration) TestDeep

TruncTime operator compares time.Time (or assignable) values after truncating them to the optional "trunc" duration. See time.Truncate for details about the truncation.

If "trunc" is missing, it defaults to 0.

During comparison, location does not matter as time.Equal method is used behind the scenes: a time instant in two different locations is the same time instant.

Whatever the "trunc" value is, the monotonic clock is stripped before the comparison against "expectedTime".

gotDate := time.Date(2018, time.March, 9, 1, 2, 3, 999999999, time.UTC).
  In(time.FixedZone("UTC+2", 2))

expected := time.Date(2018, time.March, 9, 1, 2, 3, 0, time.UTC)

td.Cmp(t, gotDate, td.TruncTime(expected))              // fails, ns differ
td.Cmp(t, gotDate, td.TruncTime(expected, time.Second)) // succeeds

TypeBehind method returns the reflect.Type of "expectedTime".

Example

Code:

t := &testing.T{}

dateToTime := func(str string) time.Time {
	t, err := time.Parse(time.RFC3339Nano, str)
	if err != nil {
		panic(err)
	}
	return t
}

got := dateToTime("2018-05-01T12:45:53.123456789Z")

expected := dateToTime("2018-05-01T12:45:53Z")
ok := td.Cmp(t, got, td.TruncTime(expected, time.Second),
	"checks date %v, truncated to the second", got)
fmt.Println(ok)

expected = dateToTime("2018-05-01T11:22:33.444444444Z")
ok = td.Cmp(t, got, td.TruncTime(expected, 24*time.Hour),
	"checks date %v, truncated to the day", got)
fmt.Println(ok)

expected = dateToTime("2018-05-01T12:45:53.123456789Z")
ok = td.Cmp(t, got, td.TruncTime(expected),
	"checks date %v ignoring monotonic part", got)
fmt.Println(ok)
true
true
true

func Values

func Values(val interface{}) TestDeep

Values is a smuggler operator. It takes a map and compares its ordered values to "val".

"val" can be a slice of items of the same type as the map values:

got := map[int]string{3: "c", 1: "a", 2: "b"}
td.Cmp(t, got, td.Values([]string{"a", "b", "c"})) // succeeds, values sorted
td.Cmp(t, got, td.Values([]string{"c", "a", "b"})) // fails as not sorted

as well as an other operator as Bag, for example, to test values in an unsorted manner:

got := map[int]string{3: "c", 1: "a", 2: "b"}
td.Cmp(t, got, td.Values(td.Bag("c", "a", "b"))) // succeeds
Example

Code:

t := &testing.T{}

got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

ok := td.Cmp(t, got, td.Values([]int{1, 2, 3}))
fmt.Println("All sorted values are found:", ok)

ok = td.Cmp(t, got, td.Values([]int{3, 1, 2}))
fmt.Println("All unsorted values are found:", ok)

ok = td.Cmp(t, got, td.Values(td.Bag(3, 1, 2)))
fmt.Println("All unsorted values are found, with the help of Bag operator:", ok)

ok = td.Cmp(t, got, td.Values(td.ArrayEach(td.Between(1, 3))))
fmt.Println("Each value is between 1 and 3:", ok)
All sorted values are found: true
All unsorted values are found: false
All unsorted values are found, with the help of Bag operator: true
Each value is between 1 and 3: true

func Zero

func Zero() TestDeep

Zero operator checks that data is zero regarding its type.

- nil is the zero value of pointers, maps, slices, channels and functions;
- 0 is the zero value of numbers;
- "" is the 0 value of strings;
- false is the zero value of booleans;
- zero value of structs is the struct with no fields initialized.

Beware that:

td.Cmp(t, AnyStruct{}, td.Zero())          // is true
td.Cmp(t, &AnyStruct{}, td.Zero())         // is false, coz pointer ≠ nil
td.Cmp(t, &AnyStruct{}, td.Ptr(td.Zero())) // is true
Example

Code:

t := &testing.T{}

ok := td.Cmp(t, 0, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, float64(0), td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, 12, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, (map[string]int)(nil), td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, map[string]int{}, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, ([]int)(nil), td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, []int{}, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, [3]int{}, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, [3]int{0, 1}, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, bytes.Buffer{}, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, &bytes.Buffer{}, td.Zero())
fmt.Println(ok)

ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.Zero()))
fmt.Println(ok)
true
true
false
true
false
true
false
true
false
true
false
true

type TestingFT

type TestingFT = testing.TB

TestingFT is a deprecated alias of testing.TB. Use testing.TB directly in new code.

type TestingT

type TestingT interface {
	Error(args ...interface{})
	Fatal(args ...interface{})
	Helper()
}

TestingT is the minimal interface used by Cmp to report errors. It is commonly implemented by *testing.T and *testing.B.

Documentation was rendered with GOOS=linux and GOARCH=amd64.

Jump to identifier

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to identifier