td

package
v1.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 27, 2023 License: BSD-2-Clause Imports: 33 Imported by: 2

Documentation

Overview

Package td (from 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.

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 go-testdeep for details.

For easy HTTP API testing, see the tdhttp helper.

For tests suites also just as easy, see tdsuite helper.

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 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 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 T.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 T.A method (or its full name alias T.Anchor) documentation for details.

Example
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	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
	}

	// Imagine a function returning a Master slice...
	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,
				},
			},
		},
	}

	// Let's check masters slice contents
	ok := td.Cmp(t, masters, td.All(
		td.Len(td.Gt(0)), // len(masters) should be > 0
		td.ArrayEach(
			// For each Master
			td.Struct(Master{}, td.StructFields{
				// Master Name should be composed of 2 words, with 1st letter uppercased
				"Name": td.Re(`^[A-Z][a-z]+ [A-Z][a-z]+\z`),
				// Annual income should be greater than $10000
				"AnnualIncome": td.Gt(10000),
				"Pets": td.ArrayEach(
					// For each Pet
					td.Struct(&Pet{}, td.StructFields{
						// Pet Name should be composed of 1 word, with 1st letter uppercased
						"Name": td.Re(`^[A-Z][a-z]+\z`),
						"Birthday": td.All(
							// Pet should be born after 2010, January 1st, but before now!
							td.Between(dateToTime("2010-01-01T00:00:00Z"), time.Now()),
							// AND minutes, seconds and nanoseconds should be 0
							td.Code(func(t time.Time) bool {
								return t.Minute() == 0 && t.Second() == 0 && t.Nanosecond() == 0
							}),
						),
						// Only dogs and cats allowed
						"Family": td.Any(Canidae, Felidae),
					}),
				),
			}),
		),
	))
	fmt.Println(ok)

}
Output:

true

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultContextConfig = ContextConfig{
	RootName:         contextDefaultRootName,
	MaxErrors:        getMaxErrorsFromEnv(),
	FailureIsFatal:   false,
	UseEqual:         false,
	BeLax:            false,
	IgnoreUnexported: false,
	TestDeepInGotOK:  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.)

Functions

func A added in v1.13.0

func A[X any](t *T, operator TestDeep) X

A is a generic shortcut to T.A.

func AddAnchorableStructType

func AddAnchorableStructType(fn any)

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 anchorable 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).

See also T.Anchor, T.AnchorsPersistTemporarily, T.DoAnchorsPersist, T.ResetAnchors and T.SetAnchorsPersist.

func Anchor added in v1.13.0

func Anchor[X any](t *T, operator TestDeep) X

Anchor is a generic shortcut to T.Anchor.

func Cmp

func Cmp(t TestingT, got, expected any, args ...any) 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

If t is a *T then its Config is inherited, so:

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

is the same as:

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

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 any, expectedValues []any, args ...any) bool

CmpAll is a shortcut for:

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

See All for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := "foo/bar"

	// Checks got string against:
	//   "o/b" regexp *AND* "bar" suffix *AND* exact "foo/bar" string
	ok := td.CmpAll(t, got, []any{td.Re("o/b"), td.HasSuffix("bar"), "foo/bar"},
		"checks value %s", got)
	fmt.Println(ok)

	// Checks got string against:
	//   "o/b" regexp *AND* "bar" suffix *AND* exact "fooX/Ybar" string
	ok = td.CmpAll(t, got, []any{td.Re("o/b"), td.HasSuffix("bar"), "fooX/Ybar"},
		"checks value %s", got)
	fmt.Println(ok)

	// When some operators or values have to be reused and mixed between
	// several calls, Flatten can be used to avoid boring and
	// inefficient []any copies:
	regOps := td.Flatten([]td.TestDeep{td.Re("o/b"), td.Re(`^fo`), td.Re(`ar$`)})
	ok = td.CmpAll(t, got, []any{td.HasPrefix("foo"), regOps, td.HasSuffix("bar")},
		"checks all operators against value %s", got)
	fmt.Println(ok)

}
Output:

true
false
true

func CmpAny

func CmpAny(t TestingT, got any, expectedValues []any, args ...any) bool

CmpAny is a shortcut for:

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

See Any for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := "foo/bar"

	// Checks got string against:
	//   "zip" regexp *OR* "bar" suffix
	ok := td.CmpAny(t, got, []any{td.Re("zip"), td.HasSuffix("bar")},
		"checks value %s", got)
	fmt.Println(ok)

	// Checks got string against:
	//   "zip" regexp *OR* "foo" suffix
	ok = td.CmpAny(t, got, []any{td.Re("zip"), td.HasSuffix("foo")},
		"checks value %s", got)
	fmt.Println(ok)

	// When some operators or values have to be reused and mixed between
	// several calls, Flatten can be used to avoid boring and
	// inefficient []any copies:
	regOps := td.Flatten([]td.TestDeep{td.Re("a/c"), td.Re(`^xx`), td.Re(`ar$`)})
	ok = td.CmpAny(t, got, []any{td.HasPrefix("xxx"), regOps, td.HasSuffix("zip")},
		"check at least one operator matches value %s", got)
	fmt.Println(ok)

}
Output:

true
false
true

func CmpArray

func CmpArray(t TestingT, got, model any, expectedEntries ArrayEntries, args ...any) bool

CmpArray is a shortcut for:

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

See Array for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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("Simple array:", ok)

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

	ok = td.CmpArray(t, &got, (*[3]int)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
		"checks array %v", got)
	fmt.Println("Array pointer, nil model:", ok)

}
Output:

Simple array: true
Array pointer: true
Array pointer, nil model: true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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("Typed array:", ok)

	ok = td.CmpArray(t, &got, &MyArray{42}, td.ArrayEntries{1: 58, 2: td.Ignore()},
		"checks pointer on typed array %v", got)
	fmt.Println("Pointer on a typed array:", 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("Pointer on a typed array, empty model:", 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("Pointer on a typed array, nil model:", ok)

}
Output:

Typed array: true
Pointer on a typed array: true
Pointer on a typed array, empty model: true
Pointer on a typed array, nil model: true

func CmpArrayEach

func CmpArrayEach(t TestingT, got, expectedValue any, args ...any) bool

CmpArrayEach is a shortcut for:

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

See ArrayEach for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func CmpBag

func CmpBag(t TestingT, got any, expectedItems []any, args ...any) bool

CmpBag is a shortcut for:

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

See Bag for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

	// Does not match as got contains 2 times 1 and 8, and these
	// duplicates are not expected
	ok = td.CmpBag(t, got, []any{1, 2, 3, 5, 8},
		"checks all items are present, in any order")
	fmt.Println(ok)

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

	// Duplicates of 1 and 8 are expected but not present in got
	ok = td.CmpBag(t, got, []any{1, 1, 2, 3, 5, 8, 8},
		"checks all items are present, in any order")
	fmt.Println(ok)

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 5}
	ok = td.CmpBag(t, got, []any{td.Flatten(expected), td.Gt(7)},
		"checks all expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
false
false
true
true

func CmpBetween

func CmpBetween(t TestingT, got, from, to any, bounds BoundsKind, args ...any) bool

CmpBetween is a shortcut for:

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

See Between for details.

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

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := 156

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

	// BoundsInIn is implicit
	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)

}
Output:

true
true
false
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := "abc"

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

	// BoundsInIn is implicit
	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)

}
Output:

true
true
false
true
false
Example (Time)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	before := time.Now()
	occurredAt := time.Now()
	after := time.Now()

	ok := td.CmpBetween(t, occurredAt, before, after, td.BoundsInIn)
	fmt.Println("It occurred between before and after:", ok)

	type MyTime time.Time
	ok = td.CmpBetween(t, MyTime(occurredAt), MyTime(before), MyTime(after), td.BoundsInIn)
	fmt.Println("Same for convertible MyTime type:", ok)

	ok = td.CmpBetween(t, MyTime(occurredAt), before, after, td.BoundsInIn)
	fmt.Println("MyTime vs time.Time:", ok)

	ok = td.CmpBetween(t, occurredAt, before, 10*time.Second, td.BoundsInIn)
	fmt.Println("Using a time.Duration as TO:", ok)

	ok = td.CmpBetween(t, MyTime(occurredAt), MyTime(before), 10*time.Second, td.BoundsInIn)
	fmt.Println("Using MyTime as FROM and time.Duration as TO:", ok)

}
Output:

It occurred between before and after: true
Same for convertible MyTime type: true
MyTime vs time.Time: false
Using a time.Duration as TO: true
Using MyTime as FROM and time.Duration as TO: true

func CmpCap

func CmpCap(t TestingT, got, expectedCap any, args ...any) bool

CmpCap is a shortcut for:

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

See Cap for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (Operator)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func CmpCode

func CmpCode(t TestingT, got, fn any, args ...any) bool

CmpCode is a shortcut for:

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

See Code for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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)

	// Same with failure reason
	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)

	// Same with failure reason thanks to error
	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)

}
Output:

true
true
true
Example (Custom)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := 123

	ok := td.CmpCode(t, got, func(t *td.T, num int) {
		t.Cmp(num, 123)
	})
	fmt.Println("with one *td.T:", ok)

	ok = td.CmpCode(t, got, func(assert, require *td.T, num int) {
		assert.Cmp(num, 123)
		require.Cmp(num, 123)
	})
	fmt.Println("with assert & require *td.T:", ok)

}
Output:

with one *td.T: true
with assert & require *td.T: true

func CmpContains

func CmpContains(t TestingT, got, expectedValue any, args ...any) bool

CmpContains is a shortcut for:

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

See Contains for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

map contains value 22: true
map contains at least one value in [20 .. 25]: true
Example (Nil)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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) // types differ: *byte ≠ *int

}
Output:

array contains untyped nil: true
array contains *int nil: true
array contains Nil(): true
array contains *byte nil: false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	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)

}
Output:

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, expectedValue any, args ...any) bool

CmpContainsKey is a shortcut for:

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

See ContainsKey for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"strings"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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) // types differ: *byte ≠ *int

}
Output:

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 any, args ...any) 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 any, args ...any) bool

CmpEmpty is a shortcut for:

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

See Empty for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.CmpEmpty(t, nil) // special case: nil is considered empty
	fmt.Println(ok)

	// fails, typed nil is not empty (expect for channel, map, slice or
	// pointers on array, channel, map slice and strings)
	ok = td.CmpEmpty(t, (*int)(nil))
	fmt.Println(ok)

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

	// Fails as 0 is a number, so not empty. Use Zero() instead
	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}) // fails, as not empty
	fmt.Println(ok)

	ok = td.CmpEmpty(t, [3]int{}) // fails, Empty() is not Zero()!
	fmt.Println(ok)

}
Output:

true
false
true
false
true
true
true
true
false
false
Example (Pointers)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type MySlice []int

	ok := td.CmpEmpty(t, MySlice{}) // Ptr() not needed
	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)

	// Works the same for array, map, channel and string

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

	ok = td.CmpEmpty(t, &MyStruct{}) // fails, use Zero() instead
	fmt.Println(ok)

}
Output:

true
true
true
false

func CmpError

func CmpError(t TestingT, got error, args ...any) 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.

See also CmpNoError.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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") // fails
	fmt.Println(ok)

}
Output:

true
false

func CmpErrorIs added in v1.13.0

func CmpErrorIs(t TestingT, got, expectedError any, args ...any) bool

CmpErrorIs is a shortcut for:

td.Cmp(t, got, td.ErrorIs(expectedError), args...)

See ErrorIs for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	err1 := fmt.Errorf("failure1")
	err2 := fmt.Errorf("failure2: %w", err1)
	err3 := fmt.Errorf("failure3: %w", err2)
	err := fmt.Errorf("failure4: %w", err3)

	ok := td.CmpErrorIs(t, err, err)
	fmt.Println("error is itself:", ok)

	ok = td.CmpErrorIs(t, err, err1)
	fmt.Println("error is also err1:", ok)

	ok = td.CmpErrorIs(t, err1, err)
	fmt.Println("err1 is err:", ok)

}
Output:

error is itself: true
error is also err1: true
err1 is err: false

func CmpFalse

func CmpFalse(t TestingT, got bool, args ...any) 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.

See also CmpTrue.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func CmpFirst added in v1.13.0

func CmpFirst(t TestingT, got, filter, expectedValue any, args ...any) bool

CmpFirst is a shortcut for:

td.Cmp(t, got, td.First(filter, expectedValue), args...)

See First for details.

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

If t is a *T then its Config field is inherited.

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 (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := td.CmpFirst(t, got, td.Gt(0), 1)
	fmt.Println("first positive number is 1:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = td.CmpFirst(t, got, isEven, -2)
	fmt.Println("first even number is -2:", ok)

	ok = td.CmpFirst(t, got, isEven, td.Lt(0))
	fmt.Println("first even number is < 0:", ok)

	ok = td.CmpFirst(t, got, isEven, td.Code(isEven))
	fmt.Println("first even number is well even:", ok)

}
Output:

first positive number is 1: true
first even number is -2: true
first even number is < 0: true
first even number is well even: true
Example (Empty)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.CmpFirst(t, ([]int)(nil), td.Gt(0), td.Gt(0))
	fmt.Println("first in nil slice:", ok)

	ok = td.CmpFirst(t, []int{}, td.Gt(0), td.Gt(0))
	fmt.Println("first in empty slice:", ok)

	ok = td.CmpFirst(t, &[]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("first in empty pointed slice:", ok)

	ok = td.CmpFirst(t, [0]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("first in empty array:", ok)

}
Output:

first in nil slice: false
first in empty slice: false
first in empty pointed slice: false
first in empty array: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      37,
		},
	}

	ok := td.CmpFirst(t, got, td.Smuggle("Age", td.Gt(30)), td.Smuggle("Fullname", "Bob Foobar"))
	fmt.Println("first person.Age > 30 → Bob:", ok)

	ok = td.CmpFirst(t, got, td.JSONPointer("/age", td.Gt(30)), td.SuperJSONOf(`{"fullname":"Bob Foobar"}`))
	fmt.Println("first person.Age > 30 → Bob, using JSON:", ok)

	ok = td.CmpFirst(t, got, td.JSONPointer("/age", td.Gt(30)), td.JSONPointer("/fullname", td.HasPrefix("Bob")))
	fmt.Println("first person.Age > 30 → Bob, using JSONPointer:", ok)

}
Output:

first person.Age > 30 → Bob: true
first person.Age > 30 → Bob, using JSON: true
first person.Age > 30 → Bob, using JSONPointer: true

func CmpGrep added in v1.13.0

func CmpGrep(t TestingT, got, filter, expectedValue any, args ...any) bool

CmpGrep is a shortcut for:

td.Cmp(t, got, td.Grep(filter, expectedValue), args...)

See Grep for details.

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

If t is a *T then its Config field is inherited.

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 (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := td.CmpGrep(t, got, td.Gt(0), []int{1, 2, 3})
	fmt.Println("check positive numbers:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = td.CmpGrep(t, got, isEven, []int{-2, 0, 2})
	fmt.Println("even numbers are -2, 0 and 2:", ok)

	ok = td.CmpGrep(t, got, isEven, td.Set(0, 2, -2))
	fmt.Println("even numbers are also 0, 2 and -2:", ok)

	ok = td.CmpGrep(t, got, isEven, td.ArrayEach(td.Code(isEven)))
	fmt.Println("even numbers are each even:", ok)

}
Output:

check positive numbers: true
even numbers are -2, 0 and 2: true
even numbers are also 0, 2 and -2: true
even numbers are each even: true
Example (Nil)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	var got []int
	ok := td.CmpGrep(t, got, td.Gt(0), ([]int)(nil))
	fmt.Println("typed []int nil:", ok)

	ok = td.CmpGrep(t, got, td.Gt(0), ([]string)(nil))
	fmt.Println("typed []string nil:", ok)

	ok = td.CmpGrep(t, got, td.Gt(0), td.Nil())
	fmt.Println("td.Nil:", ok)

	ok = td.CmpGrep(t, got, td.Gt(0), []int{})
	fmt.Println("empty non-nil slice:", ok)

}
Output:

typed []int nil: true
typed []string nil: false
td.Nil: true
empty non-nil slice: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      27,
		},
	}

	ok := td.CmpGrep(t, got, td.Smuggle("Age", td.Gt(30)), td.All(
		td.Len(1),
		td.ArrayEach(td.Smuggle("Fullname", "Bob Foobar")),
	))
	fmt.Println("person.Age > 30 → only Bob:", ok)

	ok = td.CmpGrep(t, got, td.JSONPointer("/age", td.Gt(30)), td.JSON(`[ SuperMapOf({"fullname":"Bob Foobar"}) ]`))
	fmt.Println("person.Age > 30 → only Bob, using JSON:", ok)

}
Output:

person.Age > 30 → only Bob: true
person.Age > 30 → only Bob, using JSON: true

func CmpGt

func CmpGt(t TestingT, got, minExpectedValue any, args ...any) bool

CmpGt is a shortcut for:

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

See Gt for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func CmpGte

func CmpGte(t TestingT, got, minExpectedValue any, args ...any) bool

CmpGte is a shortcut for:

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

See Gte for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func CmpHasPrefix

func CmpHasPrefix(t TestingT, got any, expected string, args ...any) bool

CmpHasPrefix is a shortcut for:

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

See HasPrefix for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func CmpHasSuffix

func CmpHasSuffix(t TestingT, got any, expected string, args ...any) bool

CmpHasSuffix is a shortcut for:

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

See HasSuffix for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func CmpIsa

func CmpIsa(t TestingT, got, model any, args ...any) bool

CmpIsa is a shortcut for:

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

See Isa for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (Interface)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	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)

	// As nil, is passed below, it is not an interface but nil… So it
	// does not match
	errGot = nil

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

	// BUT if its address is passed, now it is OK as the types match
	ok = td.CmpIsa(t, &errGot, (*error)(nil),
		"checks &errGot is a *error or implements error interface")
	fmt.Println(ok)

}
Output:

true
true
false
true

func CmpJSON

func CmpJSON(t TestingT, got, expectedJSON any, params []any, args ...any) bool

CmpJSON is a shortcut for:

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

See JSON for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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 (Embedding)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	ok := td.CmpJSON(t, got, `{"age": NotZero(), "fullname": NotEmpty()}`, nil)
	fmt.Println("check got with simple operators:", ok)

	ok = td.CmpJSON(t, got, `{"age": $^NotZero, "fullname": $^NotEmpty}`, nil)
	fmt.Println("check got with operator shortcuts:", ok)

	ok = td.CmpJSON(t, got, `
{
  "age":      Between(40, 42, "]]"), // in ]40; 42]
  "fullname": All(
    HasPrefix("Bob"),
    HasSuffix("bar")  // ← comma is optional here
  )
}`, nil)
	fmt.Println("check got with complex operators:", ok)

	ok = td.CmpJSON(t, got, `
{
  "age":      Between(40, 42, "]["), // in ]40; 42[ → 42 excluded
  "fullname": All(
    HasPrefix("Bob"),
    HasSuffix("bar"),
  )
}`, nil)
	fmt.Println("check got with complex operators:", ok)

	ok = td.CmpJSON(t, got, `
{
  "age":      Between($1, $2, $3), // in ]40; 42]
  "fullname": All(
    HasPrefix($4),
    HasSuffix("bar")  // ← comma is optional here
  )
}`, []any{40, 42, td.BoundsOutIn, "Bob"})
	fmt.Println("check got with complex operators, w/placeholder args:", ok)

}
Output:

check got with simple operators: true
check got with operator shortcuts: true
check got with complex operators: true
check got with complex operators: false
check got with complex operators, w/placeholder args: true
Example (File)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	ok := td.CmpJSON(t, got, filename, []any{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)

	// When the file is already open
	file, err := os.Open(filename)
	if err != nil {
		t.Fatal(err)
	}
	ok = td.CmpJSON(t, got, file, []any{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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string    `json:"fullname"`
		Age      int       `json:"age"`
		Children []*Person `json:"children,omitempty"`
	}

	got := &Person{
		Fullname: "Bob Foobar",
		Age:      42,
	}

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

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

	ok = td.CmpJSON(t, got, `{"age": "$1", "fullname": "$2"}`, []any{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}`, []any{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar"))})
	fmt.Println("check got with named placeholders:", ok)

	got.Children = []*Person{
		{Fullname: "Alice", Age: 28},
		{Fullname: "Brian", Age: 22},
	}
	ok = td.CmpJSON(t, got, `{"age": $age, "fullname": $name, "children": $children}`, []any{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar")), td.Tag("children", td.Bag(
		&Person{Fullname: "Brian", Age: 22},
		&Person{Fullname: "Alice", Age: 28},
	))})
	fmt.Println("check got w/named placeholders, and children w/go structs:", ok)

	ok = td.CmpJSON(t, got, `{"age": Between($1, $2), "fullname": HasSuffix($suffix), "children": Len(2)}`, []any{40, 45, td.Tag("suffix", "Foobar")})
	fmt.Println("check got w/num & named placeholders:", ok)

}
Output:

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 w/named placeholders, and children w/go structs: true
check got w/num & named placeholders: true
Example (RawStrings)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type details struct {
		Address string `json:"address"`
		Car     string `json:"car"`
	}

	got := &struct {
		Fullname string  `json:"fullname"`
		Age      int     `json:"age"`
		Details  details `json:"details"`
	}{
		Fullname: "Foo Bar",
		Age:      42,
		Details: details{
			Address: "something",
			Car:     "Peugeot",
		},
	}

	ok := td.CmpJSON(t, got, `
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty, // () are optional when no parameters
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`, nil)
	fmt.Println("Original:", ok)

	ok = td.CmpJSON(t, got, `
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({\n\"address\": NotEmpty,\n\"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\")\n})"
}`, nil)
	fmt.Println("JSON compliant:", ok)

	ok = td.CmpJSON(t, got, `
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    \"address\": NotEmpty, // () are optional when no parameters
    \"car\":     Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these
  })"
}`, nil)
	fmt.Println("JSON multilines strings:", ok)

	ok = td.CmpJSON(t, got, `
{
  "fullname": "$^HasPrefix(r<Foo>)",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    r<address>: NotEmpty, // () are optional when no parameters
    r<car>:     Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these
  })"
}`, nil)
	fmt.Println("Raw strings:", ok)

}
Output:

Original: true
JSON compliant: true
JSON multilines strings: true
Raw strings: true

func CmpJSONPointer added in v1.8.0

func CmpJSONPointer(t TestingT, got any, ptr string, expectedValue any, args ...any) bool

CmpJSONPointer is a shortcut for:

td.Cmp(t, got, td.JSONPointer(ptr, expectedValue), args...)

See JSONPointer for details.

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

If t is a *T then its Config field is inherited.

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 (Has_hasnt)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := json.RawMessage(`
{
  "name": "Bob",
  "age": 42,
  "children": [
    {
      "name": "Alice",
      "age": 16
    },
    {
      "name": "Britt",
      "age": 21,
      "children": [
        {
          "name": "John",
          "age": 1
        }
      ]
    }
  ]
}`)

	// Has Bob some children?
	ok := td.CmpJSONPointer(t, got, "/children", td.Len(td.Gt(0)))
	fmt.Println("Bob has at least one child:", ok)

	// But checking "children" exists is enough here
	ok = td.CmpJSONPointer(t, got, "/children/0/children", td.Ignore())
	fmt.Println("Alice has children:", ok)

	ok = td.CmpJSONPointer(t, got, "/children/1/children", td.Ignore())
	fmt.Println("Britt has children:", ok)

	// The reverse can be checked too
	ok = td.Cmp(t, got, td.Not(td.JSONPointer("/children/0/children", td.Ignore())))
	fmt.Println("Alice hasn't children:", ok)

	ok = td.Cmp(t, got, td.Not(td.JSONPointer("/children/1/children", td.Ignore())))
	fmt.Println("Britt hasn't children:", ok)

}
Output:

Bob has at least one child: true
Alice has children: false
Britt has children: true
Alice hasn't children: true
Britt hasn't children: false
Example (Rfc6901)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := json.RawMessage(`
{
   "foo":  ["bar", "baz"],
   "":     0,
   "a/b":  1,
   "c%d":  2,
   "e^f":  3,
   "g|h":  4,
   "i\\j": 5,
   "k\"l": 6,
   " ":    7,
   "m~n":  8
}`)

	expected := map[string]any{
		"foo": []any{"bar", "baz"},
		"":    0,
		"a/b": 1,
		"c%d": 2,
		"e^f": 3,
		"g|h": 4,
		`i\j`: 5,
		`k"l`: 6,
		" ":   7,
		"m~n": 8,
	}
	ok := td.CmpJSONPointer(t, got, "", expected)
	fmt.Println("Empty JSON pointer means all:", ok)

	ok = td.CmpJSONPointer(t, got, `/foo`, []any{"bar", "baz"})
	fmt.Println("Extract `foo` key:", ok)

	ok = td.CmpJSONPointer(t, got, `/foo/0`, "bar")
	fmt.Println("First item of `foo` key slice:", ok)

	ok = td.CmpJSONPointer(t, got, `/`, 0)
	fmt.Println("Empty key:", ok)

	ok = td.CmpJSONPointer(t, got, `/a~1b`, 1)
	fmt.Println("Slash has to be escaped using `~1`:", ok)

	ok = td.CmpJSONPointer(t, got, `/c%d`, 2)
	fmt.Println("% in key:", ok)

	ok = td.CmpJSONPointer(t, got, `/e^f`, 3)
	fmt.Println("^ in key:", ok)

	ok = td.CmpJSONPointer(t, got, `/g|h`, 4)
	fmt.Println("| in key:", ok)

	ok = td.CmpJSONPointer(t, got, `/i\j`, 5)
	fmt.Println("Backslash in key:", ok)

	ok = td.CmpJSONPointer(t, got, `/k"l`, 6)
	fmt.Println("Double-quote in key:", ok)

	ok = td.CmpJSONPointer(t, got, `/ `, 7)
	fmt.Println("Space key:", ok)

	ok = td.CmpJSONPointer(t, got, `/m~0n`, 8)
	fmt.Println("Tilde has to be escaped using `~0`:", ok)

}
Output:

Empty JSON pointer means all: true
Extract `foo` key: true
First item of `foo` key slice: true
Empty key: true
Slash has to be escaped using `~1`: true
% in key: true
^ in key: true
| in key: true
Backslash in key: true
Double-quote in key: true
Space key: true
Tilde has to be escaped using `~0`: true
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// Without json tags, encoding/json uses public fields name
	type Item struct {
		Name  string
		Value int64
		Next  *Item
	}

	got := Item{
		Name:  "first",
		Value: 1,
		Next: &Item{
			Name:  "second",
			Value: 2,
			Next: &Item{
				Name:  "third",
				Value: 3,
			},
		},
	}

	ok := td.CmpJSONPointer(t, got, "/Next/Next/Name", "third")
	fmt.Println("3rd item name is `third`:", ok)

	ok = td.CmpJSONPointer(t, got, "/Next/Next/Value", td.Gte(int64(3)))
	fmt.Println("3rd item value is greater or equal than 3:", ok)

	ok = td.CmpJSONPointer(t, got, "/Next", td.JSONPointer("/Next",
		td.JSONPointer("/Value", td.Gte(int64(3)))))
	fmt.Println("3rd item value is still greater or equal than 3:", ok)

	ok = td.CmpJSONPointer(t, got, "/Next/Next/Next/Name", td.Ignore())
	fmt.Println("4th item exists and has a name:", ok)

	// Struct comparison work with or without pointer: &Item{…} works too
	ok = td.CmpJSONPointer(t, got, "/Next/Next", Item{
		Name:  "third",
		Value: 3,
	})
	fmt.Println("3rd item full comparison:", ok)

}
Output:

3rd item name is `third`: true
3rd item value is greater or equal than 3: true
3rd item value is still greater or equal than 3: true
4th item exists and has a name: false
3rd item full comparison: true

func CmpKeys

func CmpKeys(t TestingT, got, val any, args ...any) bool

CmpKeys is a shortcut for:

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

See Keys for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// Keys tests keys in an ordered manner
	ok := td.CmpKeys(t, got, []string{"bar", "foo", "zip"})
	fmt.Println("All sorted keys are found:", ok)

	// If the expected keys are not ordered, it fails
	ok = td.CmpKeys(t, got, []string{"zip", "bar", "foo"})
	fmt.Println("All unsorted keys are found:", ok)

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

	// Check that each key is 3 bytes long
	ok = td.CmpKeys(t, got, td.ArrayEach(td.Len(3)))
	fmt.Println("Each key is 3 bytes long:", ok)

}
Output:

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 CmpLast added in v1.13.0

func CmpLast(t TestingT, got, filter, expectedValue any, args ...any) bool

CmpLast is a shortcut for:

td.Cmp(t, got, td.Last(filter, expectedValue), args...)

See Last for details.

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

If t is a *T then its Config field is inherited.

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 (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := td.CmpLast(t, got, td.Lt(0), -1)
	fmt.Println("last negative number is -1:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = td.CmpLast(t, got, isEven, 2)
	fmt.Println("last even number is 2:", ok)

	ok = td.CmpLast(t, got, isEven, td.Gt(0))
	fmt.Println("last even number is > 0:", ok)

	ok = td.CmpLast(t, got, isEven, td.Code(isEven))
	fmt.Println("last even number is well even:", ok)

}
Output:

last negative number is -1: true
last even number is 2: true
last even number is > 0: true
last even number is well even: true
Example (Empty)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.CmpLast(t, ([]int)(nil), td.Gt(0), td.Gt(0))
	fmt.Println("last in nil slice:", ok)

	ok = td.CmpLast(t, []int{}, td.Gt(0), td.Gt(0))
	fmt.Println("last in empty slice:", ok)

	ok = td.CmpLast(t, &[]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("last in empty pointed slice:", ok)

	ok = td.CmpLast(t, [0]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("last in empty array:", ok)

}
Output:

last in nil slice: false
last in empty slice: false
last in empty pointed slice: false
last in empty array: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      37,
		},
	}

	ok := td.CmpLast(t, got, td.Smuggle("Age", td.Gt(30)), td.Smuggle("Fullname", "Alice Bingo"))
	fmt.Println("last person.Age > 30 → Alice:", ok)

	ok = td.CmpLast(t, got, td.JSONPointer("/age", td.Gt(30)), td.SuperJSONOf(`{"fullname":"Alice Bingo"}`))
	fmt.Println("last person.Age > 30 → Alice, using JSON:", ok)

	ok = td.CmpLast(t, got, td.JSONPointer("/age", td.Gt(30)), td.JSONPointer("/fullname", td.HasPrefix("Alice")))
	fmt.Println("first person.Age > 30 → Alice, using JSONPointer:", ok)

}
Output:

last person.Age > 30 → Alice: true
last person.Age > 30 → Alice, using JSON: true
first person.Age > 30 → Alice, using JSONPointer: true

func CmpLax

func CmpLax(t TestingT, got, expectedValue any, args ...any) bool

CmpLax is a shortcut for:

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

See Lax for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	type myInt uint16
	gotMyInt := myInt(1236)

	expected := td.Between(1230, 1240) // int type here

	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)

}
Output:

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, expectedLen any, args ...any) bool

CmpLen is a shortcut for:

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

See Len for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (OperatorMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (OperatorSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true

func CmpLt

func CmpLt(t TestingT, got, maxExpectedValue any, args ...any) bool

CmpLt is a shortcut for:

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

See Lt for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func CmpLte

func CmpLte(t TestingT, got, maxExpectedValue any, args ...any) bool

CmpLte is a shortcut for:

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

See Lte for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func CmpMap

func CmpMap(t TestingT, got, model any, expectedEntries MapEntries, args ...any) bool

CmpMap is a shortcut for:

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

See Map for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
true

func CmpMapEach

func CmpMapEach(t TestingT, got, expectedValue any, args ...any) bool

CmpMapEach is a shortcut for:

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

See MapEach for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func CmpN

func CmpN(t TestingT, got, num, tolerance any, args ...any) bool

CmpN is a shortcut for:

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

See 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.

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true

func CmpNaN

func CmpNaN(t TestingT, got any, args ...any) bool

CmpNaN is a shortcut for:

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

See NaN for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

}
Output:

float32(math.NaN()) is float32 not-a-number: true
float32(12) is float32 not-a-number: false
Example (Float64)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

	// math.NaN() is not-a-number: true
	// float64(12) is not-a-number: false
}
Output:

func CmpNil

func CmpNil(t TestingT, got any, args ...any) bool

CmpNil is a shortcut for:

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

See Nil for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	var got fmt.Stringer // interface

	// nil value can be compared directly with nil, no need of Nil() here
	ok := td.Cmp(t, got, nil)
	fmt.Println(ok)

	// But it works with Nil() anyway
	ok = td.CmpNil(t, got)
	fmt.Println(ok)

	got = (*bytes.Buffer)(nil)

	// In the case of an interface containing a nil pointer, comparing
	// with nil fails, as the interface is not nil
	ok = td.Cmp(t, got, nil)
	fmt.Println(ok)

	// In this case Nil() succeed
	ok = td.CmpNil(t, got)
	fmt.Println(ok)

}
Output:

true
true
false
true

func CmpNoError

func CmpNoError(t TestingT, got error, args ...any) 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.

See also CmpError.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

}
Output:

false
true

func CmpNone

func CmpNone(t TestingT, got any, notExpectedValues []any, args ...any) bool

CmpNone is a shortcut for:

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

See None for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := 18

	ok := td.CmpNone(t, got, []any{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, []any{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, []any{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, []any{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)
	}

}
Output:

true
false
false
9 → true
3 → false
8 → false
15 → false

func CmpNot

func CmpNot(t TestingT, got, notExpected any, args ...any) bool

CmpNot is a shortcut for:

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

See Not for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func CmpNotAny

func CmpNotAny(t TestingT, got any, notExpectedItems []any, args ...any) bool

CmpNotAny is a shortcut for:

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

See NotAny for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using notExpected... without copying it to a new
	// []any slice, then use td.Flatten!
	notExpected := []int{3, 6, 8, 41, 43}
	ok = td.CmpNotAny(t, got, []any{td.Flatten(notExpected)},
		"checks %v contains no item listed in notExpected", got)
	fmt.Println(ok)

}
Output:

true
false
true

func CmpNotEmpty

func CmpNotEmpty(t TestingT, got any, args ...any) bool

CmpNotEmpty is a shortcut for:

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

See NotEmpty for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.CmpNotEmpty(t, nil) // fails, as nil is considered empty
	fmt.Println(ok)

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

	// Fails as 0 is a number, so not empty. Use NotZero() instead
	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{}) // succeeds, NotEmpty() is not NotZero()!
	fmt.Println(ok)

}
Output:

false
true
false
true
true
true
Example (Pointers)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type MySlice []int

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

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

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

	// Works the same for array, map, channel and string

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

	ok = td.CmpNotEmpty(t, &MyStruct{}) // fails, use NotZero() instead
	fmt.Println(ok)

}
Output:

true
true
true
false

func CmpNotNaN

func CmpNotNaN(t TestingT, got any, args ...any) bool

CmpNotNaN is a shortcut for:

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

See NotNaN for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

}
Output:

float32(math.NaN()) is NOT float32 not-a-number: false
float32(12) is NOT float32 not-a-number: true
Example (Float64)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

	// math.NaN() is NOT not-a-number: false
	// float64(12) is NOT not-a-number: true
}
Output:

func CmpNotNil

func CmpNotNil(t TestingT, got any, args ...any) bool

CmpNotNil is a shortcut for:

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

See NotNil for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// nil value can be compared directly with Not(nil), no need of NotNil() here
	ok := td.Cmp(t, got, td.Not(nil))
	fmt.Println(ok)

	// But it works with NotNil() anyway
	ok = td.CmpNotNil(t, got)
	fmt.Println(ok)

	got = (*bytes.Buffer)(nil)

	// In the case of an interface containing a nil pointer, comparing
	// with Not(nil) succeeds, as the interface is not nil
	ok = td.Cmp(t, got, td.Not(nil))
	fmt.Println(ok)

	// In this case NotNil() fails
	ok = td.CmpNotNil(t, got)
	fmt.Println(ok)

}
Output:

true
true
true
false

func CmpNotPanic

func CmpNotPanic(t TestingT, fn func(), args ...any) 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 always detected as a panic. runtime package says: before Go 1.21, programs that called panic(nil) observed recover returning nil. Starting in Go 1.21, programs that call panic(nil) observe recover returning a *runtime.PanicNilError. Programs can change back to the old behavior by setting GODEBUG=panicnil=1.

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.

See also CmpPanic.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

	// Can detect panic(nil)
	ok = td.CmpNotPanic(t, func() { panic(nil) }, "Checks for panic(nil)")
	fmt.Println("last no panic?", ok)

}
Output:

checks a panic DID NOT occur: true
still no panic? false
last no panic? false

func CmpNotZero

func CmpNotZero(t TestingT, got any, args ...any) bool

CmpNotZero is a shortcut for:

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

See NotZero for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

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

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

	ok = td.CmpNotZero(t, map[string]int{}) // succeeds, as not nil
	fmt.Println(ok)

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

	ok = td.CmpNotZero(t, []int{}) // succeeds, as not nil
	fmt.Println(ok)

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

	ok = td.CmpNotZero(t, [3]int{0, 1}) // succeeds, DATA[1] is not 0
	fmt.Println(ok)

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

	ok = td.CmpNotZero(t, &bytes.Buffer{}) // succeeds, as pointer not nil
	fmt.Println(ok)

	ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.NotZero())) // fails as deref by Ptr()
	fmt.Println(ok)

}
Output:

false
false
true
false
true
false
true
false
true
false
true
false

func CmpPPtr

func CmpPPtr(t TestingT, got, val any, args ...any) bool

CmpPPtr is a shortcut for:

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

See PPtr for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func CmpPanic

func CmpPanic(t TestingT, fn func(), expectedPanic any, args ...any) 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 always detected as a panic. runtime package says: before Go 1.21, programs that called panic(nil) observed recover returning nil. Starting in Go 1.21, programs that call panic(nil) observe recover returning a *runtime.PanicNilError. Programs can change back to the old behavior by setting GODEBUG=panicnil=1.

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.

See also CmpNotPanic.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

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

	// Can detect panic(nil)
	// Before Go 1.21, programs that called panic(nil) observed recover
	// returning nil. Starting in Go 1.21, programs that call panic(nil)
	// observe recover returning a *PanicNilError. Programs can change
	// back to the old behavior by setting GODEBUG=panicnil=1.
	// See https://pkg.go.dev/runtime#PanicNilError
	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)

	// or combined with TestDeep operators too
	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)

	// Of course, do not panic = test failure, even for expected nil
	// panic parameter
	ok = td.CmpPanic(t, func() {}, nil)
	fmt.Println("checks a panic occurred:", ok)

}
Output:

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, val any, args ...any) bool

CmpPtr is a shortcut for:

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

See Ptr for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func CmpRe

func CmpRe(t TestingT, got, reg, capture any, args ...any) bool

CmpRe is a shortcut for:

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

See 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.

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (Capture)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (Compiled)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (CompiledCapture)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (CompiledError)
package main

import (
	"errors"
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (CompiledStringer)
package main

import (
	"bytes"
	"fmt"
	"regexp"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foo bar")
	ok := td.CmpRe(t, got, expected, nil, "checks value %s", got)
	fmt.Println(ok)

}
Output:

true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

}
Output:

true

func CmpReAll

func CmpReAll(t TestingT, got, reg, capture any, args ...any) bool

CmpReAll is a shortcut for:

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

See ReAll for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// Matches, but all catured groups do not match Set
	got = "foo BAR biz"
	ok = td.CmpReAll(t, got, `(\w+)`, td.Set("biz", "foo", "bar"),
		"checks value %s", got)
	fmt.Println(ok)

}
Output:

true
false
Example (CaptureComplex)
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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)

	// Matches, but 11 is not greater than 20
	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)

}
Output:

true
false
Example (CompiledCapture)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

	// Matches, but all catured groups do not match Set
	got = "foo BAR biz"
	ok = td.CmpReAll(t, got, expected, td.Set("biz", "foo", "bar"),
		"checks value %s", got)
	fmt.Println(ok)

}
Output:

true
false
Example (CompiledCaptureComplex)
package main

import (
	"fmt"
	"regexp"
	"strconv"
	"testing"

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

func main() {
	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)

	// Matches, but 11 is not greater than 20
	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)

}
Output:

true
false

func CmpRecv added in v1.13.0

func CmpRecv(t TestingT, got, expectedValue any, timeout time.Duration, args ...any) bool

CmpRecv is a shortcut for:

td.Cmp(t, got, td.Recv(expectedValue, timeout), args...)

See Recv for details.

Recv optional parameter timeout is here mandatory. 0 value should be passed to mimic its absence in original Recv call.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := make(chan int, 3)

	ok := td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

	got <- 1
	got <- 2
	got <- 3
	close(got)

	ok = td.CmpRecv(t, got, 1, 0)
	fmt.Println("1st receive is 1:", ok)

	ok = td.Cmp(t, got, td.All(
		td.Recv(2),
		td.Recv(td.Between(3, 4)),
		td.Recv(td.RecvClosed),
	))
	fmt.Println("next receives are 2, 3 then closed:", ok)

	ok = td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

}
Output:

nothing to receive: true
1st receive is 1: true
next receives are 2, 3 then closed: true
nothing to receive: false
Example (ChannelPointer)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := make(chan int, 3)

	ok := td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

	got <- 1
	got <- 2
	got <- 3
	close(got)

	ok = td.CmpRecv(t, &got, 1, 0)
	fmt.Println("1st receive is 1:", ok)

	ok = td.Cmp(t, &got, td.All(
		td.Recv(2),
		td.Recv(td.Between(3, 4)),
		td.Recv(td.RecvClosed),
	))
	fmt.Println("next receives are 2, 3 then closed:", ok)

	ok = td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

}
Output:

nothing to receive: true
1st receive is 1: true
next receives are 2, 3 then closed: true
nothing to receive: false
Example (NilChannel)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	var ch chan int

	ok := td.CmpRecv(t, ch, td.RecvNothing, 0)
	fmt.Println("nothing to receive from nil channel:", ok)

	ok = td.CmpRecv(t, ch, 42, 0)
	fmt.Println("something to receive from nil channel:", ok)

	ok = td.CmpRecv(t, ch, td.RecvClosed, 0)
	fmt.Println("is a nil channel closed:", ok)

}
Output:

nothing to receive from nil channel: true
something to receive from nil channel: false
is a nil channel closed: false
Example (WithTimeout)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	got := make(chan int, 1)
	tick := make(chan struct{})

	go func() {
		// ①
		<-tick
		time.Sleep(100 * time.Millisecond)
		got <- 0

		// ②
		<-tick
		time.Sleep(100 * time.Millisecond)
		got <- 1

		// ③
		<-tick
		time.Sleep(100 * time.Millisecond)
		close(got)
	}()

	td.CmpRecv(t, got, td.RecvNothing, 0)

	// ①
	tick <- struct{}{}
	ok := td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("① RecvNothing:", ok)
	ok = td.CmpRecv(t, got, 0, 150*time.Millisecond)
	fmt.Println("① receive 0 w/150ms timeout:", ok)
	ok = td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("① RecvNothing:", ok)

	// ②
	tick <- struct{}{}
	ok = td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("② RecvNothing:", ok)
	ok = td.CmpRecv(t, got, 1, 150*time.Millisecond)
	fmt.Println("② receive 1 w/150ms timeout:", ok)
	ok = td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("② RecvNothing:", ok)

	// ③
	tick <- struct{}{}
	ok = td.CmpRecv(t, got, td.RecvNothing, 0)
	fmt.Println("③ RecvNothing:", ok)
	ok = td.CmpRecv(t, got, td.RecvClosed, 150*time.Millisecond)
	fmt.Println("③ check closed w/150ms timeout:", ok)

}
Output:

① RecvNothing: true
① receive 0 w/150ms timeout: true
① RecvNothing: true
② RecvNothing: true
② receive 1 w/150ms timeout: true
② RecvNothing: true
③ RecvNothing: true
③ check closed w/150ms timeout: true

func CmpSStruct

func CmpSStruct(t TestingT, got, model any, expectedFields StructFields, args ...any) bool

CmpSStruct is a shortcut for:

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

See SStruct for details.

SStruct optional parameter expectedFields is here mandatory. nil value should be passed to mimic its absence in original SStruct call.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// NumChildren is not listed in expected fields so it must be zero
	ok := td.CmpSStruct(t, got, Person{Name: "Foobar"}, td.StructFields{
		"Age": td.Between(40, 50),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	// Model can be empty
	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("Foobar has some children:", ok)

	// Works with pointers too
	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("Foobar has some children (using pointer):", ok)

	// Model does not need to be instanciated
	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("Foobar has some children (using nil model):", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar has some children: true
Foobar has some children (using pointer): true
Foobar has some children (using nil model): true
Example (Lazy_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := struct {
		name string
		age  int
	}{
		name: "Foobar",
		age:  42,
	}

	ok := td.CmpSStruct(t, got, nil, td.StructFields{
		"name": "Foobar",
		"age":  td.Between(40, 45),
	})
	fmt.Println("Lazy model:", ok)

	ok = td.CmpSStruct(t, got, nil, td.StructFields{
		"name": "Foobar",
		"zip":  666,
	})
	fmt.Println("Lazy model with unknown field:", ok)

}
Output:

Lazy model: true
Lazy model with unknown field: false
Example (Overwrite_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	ok := td.CmpSStruct(t, got, Person{
		Name: "Foobar",
		Age:  53,
	}, td.StructFields{
		">Age":        td.Between(40, 50), // ">" to overwrite Age:53 in model
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	ok = td.CmpSStruct(t, got, Person{
		Name: "Foobar",
		Age:  53,
	}, td.StructFields{
		"> Age":       td.Between(40, 50), // same, ">" can be followed by spaces
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar is between 40 & 50: true
Example (Patterns)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Firstname string
		Lastname  string
		Surname   string
		Nickname  string
		CreatedAt time.Time
		UpdatedAt time.Time
		DeletedAt *time.Time
		id        int64
		secret    string
	}

	now := time.Now()
	got := Person{
		Firstname: "Maxime",
		Lastname:  "Foo",
		Surname:   "Max",
		Nickname:  "max",
		CreatedAt: now,
		UpdatedAt: now,
		DeletedAt: nil, // not deleted yet
		id:        2345,
		secret:    "5ecr3T",
	}

	ok := td.CmpSStruct(t, got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`: nil,
		`=  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`=~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
		`!  [A-Z]*`: td.Ignore(),        // private fields
	},
		"mix shell & regexp patterns")
	fmt.Println("Patterns match only remaining fields:", ok)

	ok = td.CmpSStruct(t, got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`:   nil,
		`1 =  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`2 =~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
		`3 !~ ^[A-Z]`: td.Ignore(),        // private fields
	},
		"ordered patterns")
	fmt.Println("Ordered patterns match only remaining fields:", ok)

}
Output:

Patterns match only remaining fields: true
Ordered patterns match only remaining fields: true

func CmpSet

func CmpSet(t TestingT, got any, expectedItems []any, args ...any) bool

CmpSet is a shortcut for:

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

See Set for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

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

	// Tries its best to not raise an error when a value can be matched
	// by several Set entries
	ok = td.CmpSet(t, got, []any{td.Between(1, 4), 3, td.Between(2, 10)},
		"checks all items are present, in any order")
	fmt.Println(ok)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 5, 8}
	ok = td.CmpSet(t, got, []any{td.Flatten(expected)},
		"checks all expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
true
true
true

func CmpShallow

func CmpShallow(t TestingT, got, expectedPtr any, args ...any) bool

CmpShallow is a shortcut for:

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

See Shallow for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// Same contents, but not same pointer
	ok = td.CmpShallow(t, got, &MyStruct{Value: 12},
		"checks pointers only, not contents")
	fmt.Println(ok)

}
Output:

true
false
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

are ≠ but share the same area: true
are = but do not point to same area: false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

are ≠ but share the same area: true
are = but do not point to same area: false

func CmpSlice

func CmpSlice(t TestingT, got, model any, expectedEntries ArrayEntries, args ...any) bool

CmpSlice is a shortcut for:

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

See Slice for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
true

func CmpSmuggle

func CmpSmuggle(t TestingT, got, fn, expectedValue any, args ...any) bool

CmpSmuggle is a shortcut for:

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

See Smuggle for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// Automatically json.Unmarshal to compare
	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)

}
Output:

JSON contents is OK: true
Example (Cast)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// A string containing JSON
	got := `{ "foo": 123 }`

	// Automatically cast a string to a json.RawMessage so td.JSON can operate
	ok := td.CmpSmuggle(t, got, json.RawMessage{}, td.JSON(`{"foo":123}`))
	fmt.Println("JSON contents in string is OK:", ok)

	// Automatically read from io.Reader to a json.RawMessage
	ok = td.CmpSmuggle(t, bytes.NewReader([]byte(got)), json.RawMessage{}, td.JSON(`{"foo":123}`))
	fmt.Println("JSON contents just read is OK:", ok)

}
Output:

JSON contents in string is OK: true
JSON contents just read is OK: true
Example (Complex)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

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

	// Checks that end date is between 17th and 19th February both at 0h
	// for each of these durations in hours

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

		// Simplest way, but in case of Between() failure, error will be bound
		// to DATA<smuggled>, not very clear...
		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)

		// Name the computed value "ComputedEndDate" to render a Between() failure
		// more understandable, so error will be bound to DATA.ComputedEndDate
		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)
	}

}
Output:

false
false
true
true
true
true
Example (Convert)
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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) { //nolint: gocritic
		return strconv.Atoi(numStr)
	}, td.Between(120, 130),
		"checks that number in %#v is in [120 .. 130]")
	fmt.Println(ok)

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

}
Output:

true
true
true
true
true
Example (Field_path)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Body struct {
		Name  string
		Value any
	}
	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},
			},
		},
	}

	// Want to check whether Num is between 100 and 200?
	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)

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

	// And as Request is an anonymous field, can be simplified further
	// as it can be omitted
	ok = td.CmpSmuggle(t, got, "Body.Value.Num", td.Between(100, 200))
	fmt.Println("check Num using an other fields-path:", ok)

	// Note that maps and array/slices are supported
	got.Request.Body.Value = map[string]any{
		"foo": []any{
			3: map[int]string{666: "bar"},
		},
	}
	ok = td.CmpSmuggle(t, got, "Body.Value[foo][3][666]", "bar")
	fmt.Println("check fields-path including maps/slices:", ok)

}
Output:

check Num by hand: true
check Num using a fields-path: true
check Num using an other fields-path: true
check fields-path including maps/slices: true
Example (Interface)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

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

	// Do not check the struct itself, but its stringified form
	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)

}
Output:

stringified time.Time OK: true
stringified MyTime OK: false
Example (Lax)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// got is an int16 and Smuggle func input is an int64: it is OK
	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)

}
Output:

got int16(123) → smuggle via int64 → uint32(123): true

func CmpString

func CmpString(t TestingT, got any, expected string, args ...any) bool

CmpString is a shortcut for:

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

See String for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func CmpStruct

func CmpStruct(t TestingT, got, model any, expectedFields StructFields, args ...any) bool

CmpStruct is a shortcut for:

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

See Struct for details.

Struct optional parameter expectedFields is here mandatory. nil value should be passed to mimic its absence in original Struct call.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// As NumChildren is zero in Struct() call, it is not checked
	ok := td.CmpStruct(t, got, Person{Name: "Foobar"}, td.StructFields{
		"Age": td.Between(40, 50),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	// Model can be empty
	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("Foobar has some children:", ok)

	// Works with pointers too
	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("Foobar has some children (using pointer):", ok)

	// Model does not need to be instanciated
	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("Foobar has some children (using nil model):", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar has some children: true
Foobar has some children (using pointer): true
Foobar has some children (using nil model): true
Example (Lazy_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := struct {
		name string
		age  int
	}{
		name: "Foobar",
		age:  42,
	}

	ok := td.CmpStruct(t, got, nil, td.StructFields{
		"name": "Foobar",
		"age":  td.Between(40, 45),
	})
	fmt.Println("Lazy model:", ok)

	ok = td.CmpStruct(t, got, nil, td.StructFields{
		"name": "Foobar",
		"zip":  666,
	})
	fmt.Println("Lazy model with unknown field:", ok)

}
Output:

Lazy model: true
Lazy model with unknown field: false
Example (Overwrite_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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",
		Age:  53,
	}, td.StructFields{
		">Age":        td.Between(40, 50), // ">" to overwrite Age:53 in model
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	ok = td.CmpStruct(t, got, Person{
		Name: "Foobar",
		Age:  53,
	}, td.StructFields{
		"> Age":       td.Between(40, 50), // same, ">" can be followed by spaces
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar is between 40 & 50: true
Example (Patterns)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Firstname string
		Lastname  string
		Surname   string
		Nickname  string
		CreatedAt time.Time
		UpdatedAt time.Time
		DeletedAt *time.Time
	}

	now := time.Now()
	got := Person{
		Firstname: "Maxime",
		Lastname:  "Foo",
		Surname:   "Max",
		Nickname:  "max",
		CreatedAt: now,
		UpdatedAt: now,
		DeletedAt: nil, // not deleted yet
	}

	ok := td.CmpStruct(t, got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`: nil,
		`=  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`=~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
	},
		"mix shell & regexp patterns")
	fmt.Println("Patterns match only remaining fields:", ok)

	ok = td.CmpStruct(t, got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`:  nil,
		`1 =  *name`: td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`2 =~ At\z`:  td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
	},
		"ordered patterns")
	fmt.Println("Ordered patterns match only remaining fields:", ok)

}
Output:

Patterns match only remaining fields: true
Ordered patterns match only remaining fields: true

func CmpSubBagOf

func CmpSubBagOf(t TestingT, got any, expectedItems []any, args ...any) bool

CmpSubBagOf is a shortcut for:

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

See SubBagOf for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	ok := td.CmpSubBagOf(t, got, []any{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)

	// got contains one 8 too many
	ok = td.CmpSubBagOf(t, got, []any{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, []any{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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 5, 9, 8}
	ok = td.CmpSubBagOf(t, got, []any{td.Flatten(expected)},
		"checks at least all expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
false
true
true

func CmpSubJSONOf

func CmpSubJSONOf(t TestingT, got, expectedJSON any, params []any, args ...any) bool

CmpSubJSONOf is a shortcut for:

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

See SubJSONOf for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	ok := td.CmpSubJSONOf(t, got, filename, []any{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)

	// When the file is already open
	file, err := os.Open(filename)
	if err != nil {
		t.Fatal(err)
	}
	ok = td.CmpSubJSONOf(t, got, file, []any{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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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}`, []any{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}`, []any{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"}`, []any{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}`, []any{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)

}
Output:

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, model any, expectedEntries MapEntries, args ...any) bool

CmpSubMapOf is a shortcut for:

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

See SubMapOf for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func CmpSubSetOf

func CmpSubSetOf(t TestingT, got any, expectedItems []any, args ...any) bool

CmpSubSetOf is a shortcut for:

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

See SubSetOf for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

	// Tries its best to not raise an error when a value can be matched
	// by several SubSetOf entries
	ok = td.CmpSubSetOf(t, got, []any{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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 4, 5, 6, 7, 8}
	ok = td.CmpSubSetOf(t, got, []any{td.Flatten(expected)},
		"checks at least all expected items are present, in any order, ignoring duplicates")
	fmt.Println(ok)

}
Output:

true
true
true

func CmpSuperBagOf

func CmpSuperBagOf(t TestingT, got any, expectedItems []any, args ...any) bool

CmpSuperBagOf is a shortcut for:

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

See SuperBagOf for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{8, 5, 8}
	ok = td.CmpSuperBagOf(t, got, []any{td.Flatten(expected)},
		"checks the expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
true
true

func CmpSuperJSONOf

func CmpSuperJSONOf(t TestingT, got, expectedJSON any, params []any, args ...any) bool

CmpSuperJSONOf is a shortcut for:

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

See SuperJSONOf for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	ok := td.CmpSuperJSONOf(t, got, filename, []any{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)

	// When the file is already open
	file, err := os.Open(filename)
	if err != nil {
		t.Fatal(err)
	}
	ok = td.CmpSuperJSONOf(t, got, file, []any{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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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}`, []any{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}`, []any{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"}`, []any{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}`, []any{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)

}
Output:

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, model any, expectedEntries MapEntries, args ...any) bool

CmpSuperMapOf is a shortcut for:

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

See SuperMapOf for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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 least all expected keys/values", got)
	fmt.Println(ok)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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 least 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 least all expected keys/values",
		got)
	fmt.Println(ok)

}
Output:

true
true

func CmpSuperSetOf

func CmpSuperSetOf(t TestingT, got any, expectedItems []any, args ...any) bool

CmpSuperSetOf is a shortcut for:

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

See SuperSetOf for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

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

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3}
	ok = td.CmpSuperSetOf(t, got, []any{td.Flatten(expected)},
		"checks the expected items are present, in any order and ignoring duplicates")
	fmt.Println(ok)

}
Output:

true
true
true

func CmpSuperSliceOf added in v1.10.0

func CmpSuperSliceOf(t TestingT, got, model any, expectedEntries ArrayEntries, args ...any) bool

CmpSuperSliceOf is a shortcut for:

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

See SuperSliceOf for details.

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

If t is a *T then its Config field is inherited.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	ok := td.CmpSuperSliceOf(t, got, [4]int{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, got, [4]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, &got, &[4]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer:", ok)

	ok = td.CmpSuperSliceOf(t, &got, (*[4]int)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of an array pointer: true
Only check items #0 & #3 of an array pointer, using nil model: true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	ok := td.CmpSuperSliceOf(t, got, []int{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, got, []int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, &got, &[]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer:", ok)

	ok = td.CmpSuperSliceOf(t, &got, (*[]int)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of a slice pointer: true
Only check items #0 & #3 of a slice pointer, using nil model: true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type MyArray [4]int

	got := MyArray{42, 58, 26, 666}

	ok := td.CmpSuperSliceOf(t, got, MyArray{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks typed array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, got, MyArray{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, &got, &MyArray{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer:", ok)

	ok = td.CmpSuperSliceOf(t, &got, (*MyArray)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of an array pointer: true
Only check items #0 & #3 of an array pointer, using nil model: true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type MySlice []int

	got := MySlice{42, 58, 26, 666}

	ok := td.CmpSuperSliceOf(t, got, MySlice{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks typed array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, got, MySlice{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.CmpSuperSliceOf(t, &got, &MySlice{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer:", ok)

	ok = td.CmpSuperSliceOf(t, &got, (*MySlice)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of a slice pointer: true
Only check items #0 & #3 of a slice pointer, using nil model: true

func CmpTrue

func CmpTrue(t TestingT, got bool, args ...any) 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.

See also CmpFalse.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func CmpTruncTime

func CmpTruncTime(t TestingT, got, expectedTime any, trunc time.Duration, args ...any) bool

CmpTruncTime is a shortcut for:

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

See 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.

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	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")

	// Compare dates ignoring nanoseconds and monotonic parts
	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)

	// Compare dates ignoring time and so monotonic parts
	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)

	// Compare dates exactly but ignoring monotonic part
	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)

}
Output:

true
true
true

func CmpValues

func CmpValues(t TestingT, got, val any, args ...any) bool

CmpValues is a shortcut for:

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

See Values for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// Values tests values in an ordered manner
	ok := td.CmpValues(t, got, []int{1, 2, 3})
	fmt.Println("All sorted values are found:", ok)

	// If the expected values are not ordered, it fails
	ok = td.CmpValues(t, got, []int{3, 1, 2})
	fmt.Println("All unsorted values are found:", ok)

	// To circumvent that, one can use Bag operator
	ok = td.CmpValues(t, got, td.Bag(3, 1, 2))
	fmt.Println("All unsorted values are found, with the help of Bag operator:", ok)

	// Check that each value is between 1 and 3
	ok = td.CmpValues(t, got, td.ArrayEach(td.Between(1, 3)))
	fmt.Println("Each value is between 1 and 3:", ok)

}
Output:

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 any, args ...any) bool

CmpZero is a shortcut for:

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

See Zero for details.

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

If t is a *T then its Config field is inherited.

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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	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) // fails, as 12 is not 0 :)
	fmt.Println(ok)

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

	ok = td.CmpZero(t, map[string]int{}) // fails, as not nil
	fmt.Println(ok)

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

	ok = td.CmpZero(t, []int{}) // fails, as not nil
	fmt.Println(ok)

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

	ok = td.CmpZero(t, [3]int{0, 1}) // fails, DATA[1] is not 0
	fmt.Println(ok)

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

	ok = td.CmpZero(t, &bytes.Buffer{}) // fails, as pointer not nil
	fmt.Println(ok)

	ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.Zero())) // OK with the help of Ptr()
	fmt.Println(ok)

}
Output:

true
true
false
true
false
true
false
true
false
true
false
true

func EqDeeply

func EqDeeply(got, expected any) 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
package main

import (
	"fmt"

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

func main() {
	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!")
	}

}
Output:

Match!

func EqDeeplyError

func EqDeeplyError(got, expected any) 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
package main

import (
	"fmt"

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

func main() {
//line /testdeep/example.go:1
	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)
	}

}
Output:

DATA.Items[2]: values differ
	     got: 9
	expected: 3 ≤ got ≤ 8
[under operator Between at example.go:18]

func Flatten added in v1.5.0

func Flatten(sliceOrMap any, fn ...any) flat.Slice

Flatten allows to flatten any slice, array or map in parameters of operators expecting ...any. fn parameter allows to filter and/or transform items before flattening and is described below.

For example the Set operator is defined as:

func Set(expectedItems ...any) 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 []any as it can not be flattened directly in Set parameters:

expected := []int{22, 42, 66}
expectedIf := make([]any, 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 []any 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([]any{
      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))

Maps can be flattened too, keeping in mind there is no particular order:

td.Flatten(map[int]int{1: 2, 3: 4})

is flattened as 1, 2, 3, 4 or 3, 4, 1, 2.

Optional fn parameter can be used to filter and/or transform items before flattening. If passed, it has to be one element length and this single element can be:

  • untyped nil: it is a no-op, as if it was not passed
  • a function
  • a string shortcut

If it is a function, it must be a non-nil function with a signature like:

func(T) V
func(T) (V, bool)

T can be the same as V, but it is not mandatory. The (V, bool) returned case allows to exclude some items when returning false.

If the function signature does not match these cases, Flatten panics.

If the type of an item of sliceOrMap is not convertible to T, the item is dropped silently, as if fn returned false.

This single element can also be a string among:

"Smuggle:FIELD"
"JSONPointer:/PATH"

that are shortcuts for respectively:

func(in any) any { return td.Smuggle("FIELD", in) }
func(in any) any { return td.JSONPointer("/PATH", in) }

See Smuggle and JSONPointer for a description of what "FIELD" and "/PATH" can really be.

Flatten with an fn can be useful when testing some fields of structs in a slice with Set or Bag operators families. As an example, here we test only "Name" field for each item of a person slice:

type person struct {
  Name string `json:"name"`
  Age  int    `json:"age"`
}
got := []person{{"alice", 22}, {"bob", 18}, {"brian", 34}, {"britt", 32}}

td.Cmp(t, got,
  td.Bag(td.Flatten(
    []string{"alice", "britt", "brian", "bob"},
    func(name string) any { return td.Smuggle("Name", name) })))
// distributes td.Smuggle for each Name, so is equivalent of:
td.Cmp(t, got, td.Bag(
  td.Smuggle("Name", "alice"),
  td.Smuggle("Name", "britt"),
  td.Smuggle("Name", "brian"),
  td.Smuggle("Name", "bob"),
))

// Same here using Smuggle string shortcut
td.Cmp(t, got,
  td.Bag(td.Flatten(
    []string{"alice", "britt", "brian", "bob"}, "Smuggle:Name")))

// Same here, but using JSONPointer operator
td.Cmp(t, got,
  td.Bag(td.Flatten(
    []string{"alice", "britt", "brian", "bob"},
    func(name string) any { return td.JSONPointer("/name", name) })))

// Same here using JSONPointer string shortcut
td.Cmp(t, got,
  td.Bag(td.Flatten(
    []string{"alice", "britt", "brian", "bob"}, "JSONPointer:/name")))

// Same here, but using SuperJSONOf operator
td.Cmp(t, got,
  td.Bag(td.Flatten(
    []string{"alice", "britt", "brian", "bob"},
    func(name string) any { return td.SuperJSONOf(`{"name":$1}`, name) })))

// Same here, but using Struct operator
td.Cmp(t, got,
  td.Bag(td.Flatten(
    []string{"alice", "britt", "brian", "bob"},
    func(name string) any { return td.Struct(person{Name: name}) })))

See also Grep.

func S added in v1.13.0

func S(args ...any) string

S returns a string based on args as Cmp* functions do with their own args parameter to name their test. So behind the scene, tdutil.BuildTestName is used.

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 returned string, else args are passed to fmt.Fprint.

It can be used as a shorter fmt.Sprintf:

t.Run(fmt.Sprintf("Foo #%d", i), func(t *td.T) {})
t.Run(td.S("Foo #%d", i), func(t *td.T) {})

or to print any values as fmt.Sprint handles them:

a, ok := []int{1, 2, 3}, true
t.Run(fmt.Sprint(a, ok), func(t *td.T) {})
t.Run(td.S(a, ok), func(t *td.T) {})

The only gain is less characters to type.

Types

type ArrayEntries

type ArrayEntries map[int]any

ArrayEntries allows to pass array or slice entries to check in functions Array, Slice and SuperSliceOf. 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   BoundsKind // allows to match between "from" and "to" both included.
	BoundsInOut             // allows to match between "from" included and "to" excluded.
	BoundsOutIn             // allows to match between "from" excluded and "to" included.
	BoundsOutOut            // allows to match between "from" and "to" both excluded.
)

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.
	//
	// See (*T).UseEqual method to only apply this property to some
	// specific types.
	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
	// IgnoreUnexported allows to ignore unexported struct fields. Be
	// careful about structs entirely composed of unexported fields
	// (like time.Time for example). With this flag set to true, they
	// are all equal. In such case it is advised to set UseEqual flag,
	// to use (*T).UseEqual method or to add a Cmp hook using
	// (*T).WithCmpHooks method.
	//
	// See (*T).IgnoreUnexported method to only apply this property to some
	// specific types.
	IgnoreUnexported bool
	// TestDeepInGotOK allows to accept TestDeep operator in got Cmp*
	// parameter. By default it is forbidden and a panic occurs, because
	// most of the time it is a mistake to compare (expected, got)
	// instead of official (got, expected).
	TestDeepInGotOK 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 c and o are equal. Only public fields are taken into account to check equality.

func (ContextConfig) OriginalPath added in v1.12.0

func (c ContextConfig) OriginalPath() string

OriginalPath returns the current path when the ContextConfig has been built. It always returns ContextConfig.RootName except if c has been built by Code operator. See Code documentation for an example of use.

type MapEntries

type MapEntries map[any]any

MapEntries allows to pass map entries to check in functions Map, SubMapOf and SuperMapOf. 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 RecvKind added in v1.13.0

type RecvKind = types.RecvKind

A RecvKind allows to match that nothing has been received on a channel or that a channel has been closed when using Recv operator.

const (
	RecvNothing RecvKind // nothing read on channel
	RecvClosed           // channel closed
)

type SmuggledGot

type SmuggledGot struct {
	Name string
	Got  any
}

SmuggledGot can be returned by a Smuggle function to name the transformed / returned value.

type StructFields

type StructFields map[string]any

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 (or a regexp or a shell pattern matching a field name, see Struct & SStruct docs for details) 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 returns 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.

See also Require, AssertRequire and T.Assert.

func AssertRequire

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

AssertRequire returns 2 instances of *T. assert with FailureIsFatal flag set to false, and 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.

See also Assert and Require.

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 parameter 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 T.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, including hooks.

func Require

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

Require returns 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.

See also Assert, AssertRequire and T.Require.

func (*T) A

func (t *T) A(operator TestDeep, model ...any) any

A is a synonym for T.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),
    },
  })
}

See also T.AnchorsPersistTemporarily, T.DoAnchorsPersist, T.ResetAnchors, T.SetAnchorsPersist and AddAnchorableStructType.

func (*T) All

func (t *T) All(got any, expectedValues []any, args ...any) bool

All is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := "foo/bar"

	// Checks got string against:
	//   "o/b" regexp *AND* "bar" suffix *AND* exact "foo/bar" string
	ok := t.All(got, []any{td.Re("o/b"), td.HasSuffix("bar"), "foo/bar"},
		"checks value %s", got)
	fmt.Println(ok)

	// Checks got string against:
	//   "o/b" regexp *AND* "bar" suffix *AND* exact "fooX/Ybar" string
	ok = t.All(got, []any{td.Re("o/b"), td.HasSuffix("bar"), "fooX/Ybar"},
		"checks value %s", got)
	fmt.Println(ok)

	// When some operators or values have to be reused and mixed between
	// several calls, Flatten can be used to avoid boring and
	// inefficient []any copies:
	regOps := td.Flatten([]td.TestDeep{td.Re("o/b"), td.Re(`^fo`), td.Re(`ar$`)})
	ok = t.All(got, []any{td.HasPrefix("foo"), regOps, td.HasSuffix("bar")},
		"checks all operators against value %s", got)
	fmt.Println(ok)

}
Output:

true
false
true

func (*T) Anchor

func (t *T) Anchor(operator TestDeep, model ...any) any

Anchor returns a typed value allowing to anchor the TestDeep operator operator in a go classic literal 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 T.SetAnchorsPersist and T.AnchorsPersistTemporarily methods.

See T.A method for a shorter synonym of Anchor.

See also T.AnchorsPersistTemporarily, T.DoAnchorsPersist, T.ResetAnchors, T.SetAnchorsPersist and AddAnchorableStructType.

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()()
// or
t.Cleanup(t.AnchorsPersistTemporarily())

See also T.Anchor, T.DoAnchorsPersist, T.ResetAnchors, T.SetAnchorsPersist and AddAnchorableStructType.

func (*T) Any

func (t *T) Any(got any, expectedValues []any, args ...any) bool

Any is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := "foo/bar"

	// Checks got string against:
	//   "zip" regexp *OR* "bar" suffix
	ok := t.Any(got, []any{td.Re("zip"), td.HasSuffix("bar")},
		"checks value %s", got)
	fmt.Println(ok)

	// Checks got string against:
	//   "zip" regexp *OR* "foo" suffix
	ok = t.Any(got, []any{td.Re("zip"), td.HasSuffix("foo")},
		"checks value %s", got)
	fmt.Println(ok)

	// When some operators or values have to be reused and mixed between
	// several calls, Flatten can be used to avoid boring and
	// inefficient []any copies:
	regOps := td.Flatten([]td.TestDeep{td.Re("a/c"), td.Re(`^xx`), td.Re(`ar$`)})
	ok = t.Any(got, []any{td.HasPrefix("xxx"), regOps, td.HasSuffix("zip")},
		"check at least one operator matches value %s", got)
	fmt.Println(ok)

}
Output:

true
false
true

func (*T) Array

func (t *T) Array(got, model any, expectedEntries ArrayEntries, args ...any) bool

Array is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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("Simple array:", ok)

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

	ok = t.Array(&got, (*[3]int)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
		"checks array %v", got)
	fmt.Println("Array pointer, nil model:", ok)

}
Output:

Simple array: true
Array pointer: true
Array pointer, nil model: true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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("Typed array:", ok)

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

	ok = t.Array(&got, &MyArray{}, td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()},
		"checks pointer on typed array %v", got)
	fmt.Println("Pointer on a typed array, empty model:", 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("Pointer on a typed array, nil model:", ok)

}
Output:

Typed array: true
Pointer on a typed array: true
Pointer on a typed array, empty model: true
Pointer on a typed array, nil model: true

func (*T) ArrayEach

func (t *T) ArrayEach(got, expectedValue any, args ...any) bool

ArrayEach is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func (*T) Assert added in v1.13.0

func (t *T) Assert() *T

Assert returns a new *T instance inheriting the t config but with FailureIsFatal flag set to false.

It returns a new instance of *T so does not alter the original t

It is a shortcut for:

t.FailureIsFatal(false)

See also T.FailureIsFatal and T.Require.

func (*T) Bag

func (t *T) Bag(got any, expectedItems []any, args ...any) bool

Bag is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

	// Does not match as got contains 2 times 1 and 8, and these
	// duplicates are not expected
	ok = t.Bag(got, []any{1, 2, 3, 5, 8},
		"checks all items are present, in any order")
	fmt.Println(ok)

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

	// Duplicates of 1 and 8 are expected but not present in got
	ok = t.Bag(got, []any{1, 1, 2, 3, 5, 8, 8},
		"checks all items are present, in any order")
	fmt.Println(ok)

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 5}
	ok = t.Bag(got, []any{td.Flatten(expected), td.Gt(7)},
		"checks all expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
false
false
true
true

func (*T) BeLax

func (t *T) BeLax(enable ...bool) *T

BeLax allows 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 or T.CmpLax 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, from, to any, bounds BoundsKind, args ...any) bool

Between is a shortcut for:

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

See Between for details.

Between optional parameter bounds is here mandatory. 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// BoundsInIn is implicit
	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)

}
Output:

true
true
false
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// BoundsInIn is implicit
	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)

}
Output:

true
true
false
true
false
Example (Time)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := td.NewT(&testing.T{})

	before := time.Now()
	occurredAt := time.Now()
	after := time.Now()

	ok := t.Between(occurredAt, before, after, td.BoundsInIn)
	fmt.Println("It occurred between before and after:", ok)

	type MyTime time.Time
	ok = t.Between(MyTime(occurredAt), MyTime(before), MyTime(after), td.BoundsInIn)
	fmt.Println("Same for convertible MyTime type:", ok)

	ok = t.Between(MyTime(occurredAt), before, after, td.BoundsInIn)
	fmt.Println("MyTime vs time.Time:", ok)

	ok = t.Between(occurredAt, before, 10*time.Second, td.BoundsInIn)
	fmt.Println("Using a time.Duration as TO:", ok)

	ok = t.Between(MyTime(occurredAt), MyTime(before), 10*time.Second, td.BoundsInIn)
	fmt.Println("Using MyTime as FROM and time.Duration as TO:", ok)

}
Output:

It occurred between before and after: true
Same for convertible MyTime type: true
MyTime vs time.Time: false
Using a time.Duration as TO: true
Using MyTime as FROM and time.Duration as TO: true

func (*T) Cap

func (t *T) Cap(got, expectedCap any, args ...any) bool

Cap is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (Operator)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func (*T) Cmp

func (t *T) Cmp(got, expected any, args ...any) 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 ContextConfig.

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 any, args ...any) 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 ...any) 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.

See also T.CmpNoError.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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") // fails
	fmt.Println(ok)

}
Output:

true
false

func (*T) CmpErrorIs added in v1.13.0

func (t *T) CmpErrorIs(got, expectedError any, args ...any) bool

CmpErrorIs is a shortcut for:

t.Cmp(got, td.ErrorIs(expectedError), args...)

See ErrorIs 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	err1 := fmt.Errorf("failure1")
	err2 := fmt.Errorf("failure2: %w", err1)
	err3 := fmt.Errorf("failure3: %w", err2)
	err := fmt.Errorf("failure4: %w", err3)

	ok := t.CmpErrorIs(err, err)
	fmt.Println("error is itself:", ok)

	ok = t.CmpErrorIs(err, err1)
	fmt.Println("error is also err1:", ok)

	ok = t.CmpErrorIs(err1, err)
	fmt.Println("err1 is err:", ok)

}
Output:

error is itself: true
error is also err1: true
err1 is err: false

func (*T) CmpLax

func (t *T) CmpLax(got, expectedValue any, args ...any) bool

CmpLax is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	type myInt uint16
	gotMyInt := myInt(1236)

	expected := td.Between(1230, 1240) // int type here

	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)

}
Output:

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 ...any) 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 T.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.

See also T.CmpError.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

}
Output:

false
true

func (*T) CmpNotPanic

func (t *T) CmpNotPanic(fn func(), args ...any) 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 always detected as a panic. runtime package says: before Go 1.21, programs that called panic(nil) observed recover returning nil. Starting in Go 1.21, programs that call panic(nil) observe recover returning a *runtime.PanicNilError. Programs can change back to the old behavior by setting GODEBUG=panicnil=1.

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.

See also T.CmpPanic.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

	// Can detect panic(nil)
	ok = t.CmpNotPanic(func() { panic(nil) }, "Checks for panic(nil)")
	fmt.Println("last no panic?", ok)

}
Output:

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 any, args ...any) 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 always detected as a panic. runtime package says: before Go 1.21, programs that called panic(nil) observed recover returning nil. Starting in Go 1.21, programs that call panic(nil) observe recover returning a *runtime.PanicNilError. Programs can change back to the old behavior by setting GODEBUG=panicnil=1.

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.

See also T.CmpNotPanic.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

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

	// Can detect panic(nil)
	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)

	// or combined with TestDeep operators too
	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)

	// Of course, do not panic = test failure, even for expected nil
	// panic parameter
	ok = t.CmpPanic(func() {}, nil)
	fmt.Println("checks a panic occurred:", ok)

}
Output:

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, fn any, args ...any) bool

Code is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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)

	// Same with failure reason
	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)

	// Same with failure reason thanks to error
	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)

}
Output:

true
true
true
Example (Custom)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := 123

	ok := t.Code(got, func(t *td.T, num int) {
		t.Cmp(num, 123)
	})
	fmt.Println("with one *td.T:", ok)

	ok = t.Code(got, func(assert, require *td.T, num int) {
		assert.Cmp(num, 123)
		require.Cmp(num, 123)
	})
	fmt.Println("with assert & require *td.T:", ok)

}
Output:

with one *td.T: true
with assert & require *td.T: true

func (*T) Contains

func (t *T) Contains(got, expectedValue any, args ...any) bool

Contains is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

map contains value 22: true
map contains at least one value in [20 .. 25]: true
Example (Nil)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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) // types differ: *byte ≠ *int

}
Output:

array contains untyped nil: true
array contains *int nil: true
array contains Nil(): true
array contains *byte nil: false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// bytes.Buffer implements fmt.Stringer
	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)

}
Output:

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, expectedValue any, args ...any) bool

ContainsKey is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"strings"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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) // types differ: *byte ≠ *int

}
Output:

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.

See also T.Anchor, T.AnchorsPersistTemporarily, T.ResetAnchors, T.SetAnchorsPersist and AddAnchorableStructType.

func (*T) Empty

func (t *T) Empty(got any, args ...any) bool

Empty is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	ok := t.Empty(nil) // special case: nil is considered empty
	fmt.Println(ok)

	// fails, typed nil is not empty (expect for channel, map, slice or
	// pointers on array, channel, map slice and strings)
	ok = t.Empty((*int)(nil))
	fmt.Println(ok)

	ok = t.Empty("")
	fmt.Println(ok)

	// Fails as 0 is a number, so not empty. Use Zero() instead
	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}) // fails, as not empty
	fmt.Println(ok)

	ok = t.Empty([3]int{}) // fails, Empty() is not Zero()!
	fmt.Println(ok)

}
Output:

true
false
true
false
true
true
true
true
false
false
Example (Pointers)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type MySlice []int

	ok := t.Empty(MySlice{}) // Ptr() not needed
	fmt.Println(ok)

	ok = t.Empty(&MySlice{})
	fmt.Println(ok)

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

	// Works the same for array, map, channel and string

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

	ok = t.Empty(&MyStruct{}) // fails, use Zero() instead
	fmt.Println(ok)

}
Output:

true
true
true
false

func (*T) ErrorTrace added in v1.11.0

func (t *T) ErrorTrace(args ...any)

ErrorTrace uses t.TB.Error() to log a stack trace.

args... are optional and allow to prefix the trace by a message. If empty, this message defaults to "Stack trace:\n". If this message does not end with a "\n", one is automatically added. 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.

See also T.LogTrace and T.FatalTrace.

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() method 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).

See also T.Assert and T.Require.

func (*T) False

func (t *T) False(got any, args ...any) 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.

See also T.True.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func (*T) FatalTrace added in v1.11.0

func (t *T) FatalTrace(args ...any)

FatalTrace uses t.TB.Fatal() to log a stack trace.

args... are optional and allow to prefix the trace by a message. If empty, this message defaults to "Stack trace:\n". If this message does not end with a "\n", one is automatically added. 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.

See also T.LogTrace and T.ErrorTrace.

func (*T) First added in v1.13.0

func (t *T) First(got, filter, expectedValue any, args ...any) bool

First is a shortcut for:

t.Cmp(got, td.First(filter, expectedValue), args...)

See First 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 (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := t.First(got, td.Gt(0), 1)
	fmt.Println("first positive number is 1:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = t.First(got, isEven, -2)
	fmt.Println("first even number is -2:", ok)

	ok = t.First(got, isEven, td.Lt(0))
	fmt.Println("first even number is < 0:", ok)

	ok = t.First(got, isEven, td.Code(isEven))
	fmt.Println("first even number is well even:", ok)

}
Output:

first positive number is 1: true
first even number is -2: true
first even number is < 0: true
first even number is well even: true
Example (Empty)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	ok := t.First(([]int)(nil), td.Gt(0), td.Gt(0))
	fmt.Println("first in nil slice:", ok)

	ok = t.First([]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("first in empty slice:", ok)

	ok = t.First(&[]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("first in empty pointed slice:", ok)

	ok = t.First([0]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("first in empty array:", ok)

}
Output:

first in nil slice: false
first in empty slice: false
first in empty pointed slice: false
first in empty array: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      37,
		},
	}

	ok := t.First(got, td.Smuggle("Age", td.Gt(30)), td.Smuggle("Fullname", "Bob Foobar"))
	fmt.Println("first person.Age > 30 → Bob:", ok)

	ok = t.First(got, td.JSONPointer("/age", td.Gt(30)), td.SuperJSONOf(`{"fullname":"Bob Foobar"}`))
	fmt.Println("first person.Age > 30 → Bob, using JSON:", ok)

	ok = t.First(got, td.JSONPointer("/age", td.Gt(30)), td.JSONPointer("/fullname", td.HasPrefix("Bob")))
	fmt.Println("first person.Age > 30 → Bob, using JSONPointer:", ok)

}
Output:

first person.Age > 30 → Bob: true
first person.Age > 30 → Bob, using JSON: true
first person.Age > 30 → Bob, using JSONPointer: true

func (*T) Grep added in v1.13.0

func (t *T) Grep(got, filter, expectedValue any, args ...any) bool

Grep is a shortcut for:

t.Cmp(got, td.Grep(filter, expectedValue), args...)

See Grep 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 (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := t.Grep(got, td.Gt(0), []int{1, 2, 3})
	fmt.Println("check positive numbers:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = t.Grep(got, isEven, []int{-2, 0, 2})
	fmt.Println("even numbers are -2, 0 and 2:", ok)

	ok = t.Grep(got, isEven, td.Set(0, 2, -2))
	fmt.Println("even numbers are also 0, 2 and -2:", ok)

	ok = t.Grep(got, isEven, td.ArrayEach(td.Code(isEven)))
	fmt.Println("even numbers are each even:", ok)

}
Output:

check positive numbers: true
even numbers are -2, 0 and 2: true
even numbers are also 0, 2 and -2: true
even numbers are each even: true
Example (Nil)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	var got []int
	ok := t.Grep(got, td.Gt(0), ([]int)(nil))
	fmt.Println("typed []int nil:", ok)

	ok = t.Grep(got, td.Gt(0), ([]string)(nil))
	fmt.Println("typed []string nil:", ok)

	ok = t.Grep(got, td.Gt(0), td.Nil())
	fmt.Println("td.Nil:", ok)

	ok = t.Grep(got, td.Gt(0), []int{})
	fmt.Println("empty non-nil slice:", ok)

}
Output:

typed []int nil: true
typed []string nil: false
td.Nil: true
empty non-nil slice: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      27,
		},
	}

	ok := t.Grep(got, td.Smuggle("Age", td.Gt(30)), td.All(
		td.Len(1),
		td.ArrayEach(td.Smuggle("Fullname", "Bob Foobar")),
	))
	fmt.Println("person.Age > 30 → only Bob:", ok)

	ok = t.Grep(got, td.JSONPointer("/age", td.Gt(30)), td.JSON(`[ SuperMapOf({"fullname":"Bob Foobar"}) ]`))
	fmt.Println("person.Age > 30 → only Bob, using JSON:", ok)

}
Output:

person.Age > 30 → only Bob: true
person.Age > 30 → only Bob, using JSON: true

func (*T) Gt

func (t *T) Gt(got, minExpectedValue any, args ...any) bool

Gt is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func (*T) Gte

func (t *T) Gte(got, minExpectedValue any, args ...any) bool

Gte is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func (*T) HasPrefix

func (t *T) HasPrefix(got any, expected string, args ...any) bool

HasPrefix is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func (*T) HasSuffix

func (t *T) HasSuffix(got any, expected string, args ...any) bool

HasSuffix is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func (*T) IgnoreUnexported added in v1.10.0

func (t *T) IgnoreUnexported(types ...any) *T

IgnoreUnexported tells go-testdeep to ignore unexported fields of structs whose type is one of types.

It always returns a new instance of *T so does not alter the original t.

t = t.IgnoreUnexported(MyStruct1{}, MyStruct2{})

types items can also be reflect.Type items. In this case, the target type is the one reflected by the reflect.Type.

t = t.IgnoreUnexported(reflect.TypeOf(MyStruct1{}))

As a special case, calling t.IgnoreUnexported() or t.IgnoreUnexported(true) returns an instance ignoring unexported fields globally, for all struct types. t.IgnoreUnexported(false) returns an instance not ignoring unexported fields anymore, except for types already recorded using a previous IgnoreUnexported call.

func (*T) Isa

func (t *T) Isa(got, model any, args ...any) bool

Isa is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (Interface)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	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)

	// As nil, is passed below, it is not an interface but nil… So it
	// does not match
	errGot = nil

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

	// BUT if its address is passed, now it is OK as the types match
	ok = t.Isa(&errGot, (*error)(nil),
		"checks &errGot is a *error or implements error interface")
	fmt.Println(ok)

}
Output:

true
true
false
true

func (*T) JSON

func (t *T) JSON(got, expectedJSON any, params []any, args ...any) bool

JSON is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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 (Embedding)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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": NotZero(), "fullname": NotEmpty()}`, nil)
	fmt.Println("check got with simple operators:", ok)

	ok = t.JSON(got, `{"age": $^NotZero, "fullname": $^NotEmpty}`, nil)
	fmt.Println("check got with operator shortcuts:", ok)

	ok = t.JSON(got, `
{
  "age":      Between(40, 42, "]]"), // in ]40; 42]
  "fullname": All(
    HasPrefix("Bob"),
    HasSuffix("bar")  // ← comma is optional here
  )
}`, nil)
	fmt.Println("check got with complex operators:", ok)

	ok = t.JSON(got, `
{
  "age":      Between(40, 42, "]["), // in ]40; 42[ → 42 excluded
  "fullname": All(
    HasPrefix("Bob"),
    HasSuffix("bar"),
  )
}`, nil)
	fmt.Println("check got with complex operators:", ok)

	ok = t.JSON(got, `
{
  "age":      Between($1, $2, $3), // in ]40; 42]
  "fullname": All(
    HasPrefix($4),
    HasSuffix("bar")  // ← comma is optional here
  )
}`, []any{40, 42, td.BoundsOutIn, "Bob"})
	fmt.Println("check got with complex operators, w/placeholder args:", ok)

}
Output:

check got with simple operators: true
check got with operator shortcuts: true
check got with complex operators: true
check got with complex operators: false
check got with complex operators, w/placeholder args: true
Example (File)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	ok := t.JSON(got, filename, []any{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)

	// When the file is already open
	file, err := os.Open(filename)
	if err != nil {
		t.Fatal(err)
	}
	ok = t.JSON(got, file, []any{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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Fullname string    `json:"fullname"`
		Age      int       `json:"age"`
		Children []*Person `json:"children,omitempty"`
	}

	got := &Person{
		Fullname: "Bob Foobar",
		Age:      42,
	}

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

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

	ok = t.JSON(got, `{"age": "$1", "fullname": "$2"}`, []any{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}`, []any{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar"))})
	fmt.Println("check got with named placeholders:", ok)

	got.Children = []*Person{
		{Fullname: "Alice", Age: 28},
		{Fullname: "Brian", Age: 22},
	}
	ok = t.JSON(got, `{"age": $age, "fullname": $name, "children": $children}`, []any{td.Tag("age", td.Between(40, 45)), td.Tag("name", td.HasSuffix("Foobar")), td.Tag("children", td.Bag(
		&Person{Fullname: "Brian", Age: 22},
		&Person{Fullname: "Alice", Age: 28},
	))})
	fmt.Println("check got w/named placeholders, and children w/go structs:", ok)

	ok = t.JSON(got, `{"age": Between($1, $2), "fullname": HasSuffix($suffix), "children": Len(2)}`, []any{40, 45, td.Tag("suffix", "Foobar")})
	fmt.Println("check got w/num & named placeholders:", ok)

}
Output:

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 w/named placeholders, and children w/go structs: true
check got w/num & named placeholders: true
Example (RawStrings)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type details struct {
		Address string `json:"address"`
		Car     string `json:"car"`
	}

	got := &struct {
		Fullname string  `json:"fullname"`
		Age      int     `json:"age"`
		Details  details `json:"details"`
	}{
		Fullname: "Foo Bar",
		Age:      42,
		Details: details{
			Address: "something",
			Car:     "Peugeot",
		},
	}

	ok := t.JSON(got, `
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty, // () are optional when no parameters
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`, nil)
	fmt.Println("Original:", ok)

	ok = t.JSON(got, `
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({\n\"address\": NotEmpty,\n\"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\")\n})"
}`, nil)
	fmt.Println("JSON compliant:", ok)

	ok = t.JSON(got, `
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    \"address\": NotEmpty, // () are optional when no parameters
    \"car\":     Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these
  })"
}`, nil)
	fmt.Println("JSON multilines strings:", ok)

	ok = t.JSON(got, `
{
  "fullname": "$^HasPrefix(r<Foo>)",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    r<address>: NotEmpty, // () are optional when no parameters
    r<car>:     Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these
  })"
}`, nil)
	fmt.Println("Raw strings:", ok)

}
Output:

Original: true
JSON compliant: true
JSON multilines strings: true
Raw strings: true

func (*T) JSONPointer added in v1.8.0

func (t *T) JSONPointer(got any, ptr string, expectedValue any, args ...any) bool

JSONPointer is a shortcut for:

t.Cmp(got, td.JSONPointer(ptr, expectedValue), args...)

See JSONPointer 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 (Has_hasnt)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := json.RawMessage(`
{
  "name": "Bob",
  "age": 42,
  "children": [
    {
      "name": "Alice",
      "age": 16
    },
    {
      "name": "Britt",
      "age": 21,
      "children": [
        {
          "name": "John",
          "age": 1
        }
      ]
    }
  ]
}`)

	// Has Bob some children?
	ok := t.JSONPointer(got, "/children", td.Len(td.Gt(0)))
	fmt.Println("Bob has at least one child:", ok)

	// But checking "children" exists is enough here
	ok = t.JSONPointer(got, "/children/0/children", td.Ignore())
	fmt.Println("Alice has children:", ok)

	ok = t.JSONPointer(got, "/children/1/children", td.Ignore())
	fmt.Println("Britt has children:", ok)

	// The reverse can be checked too
	ok = t.Cmp(got, td.Not(td.JSONPointer("/children/0/children", td.Ignore())))
	fmt.Println("Alice hasn't children:", ok)

	ok = t.Cmp(got, td.Not(td.JSONPointer("/children/1/children", td.Ignore())))
	fmt.Println("Britt hasn't children:", ok)

}
Output:

Bob has at least one child: true
Alice has children: false
Britt has children: true
Alice hasn't children: true
Britt hasn't children: false
Example (Rfc6901)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := json.RawMessage(`
{
   "foo":  ["bar", "baz"],
   "":     0,
   "a/b":  1,
   "c%d":  2,
   "e^f":  3,
   "g|h":  4,
   "i\\j": 5,
   "k\"l": 6,
   " ":    7,
   "m~n":  8
}`)

	expected := map[string]any{
		"foo": []any{"bar", "baz"},
		"":    0,
		"a/b": 1,
		"c%d": 2,
		"e^f": 3,
		"g|h": 4,
		`i\j`: 5,
		`k"l`: 6,
		" ":   7,
		"m~n": 8,
	}
	ok := t.JSONPointer(got, "", expected)
	fmt.Println("Empty JSON pointer means all:", ok)

	ok = t.JSONPointer(got, `/foo`, []any{"bar", "baz"})
	fmt.Println("Extract `foo` key:", ok)

	ok = t.JSONPointer(got, `/foo/0`, "bar")
	fmt.Println("First item of `foo` key slice:", ok)

	ok = t.JSONPointer(got, `/`, 0)
	fmt.Println("Empty key:", ok)

	ok = t.JSONPointer(got, `/a~1b`, 1)
	fmt.Println("Slash has to be escaped using `~1`:", ok)

	ok = t.JSONPointer(got, `/c%d`, 2)
	fmt.Println("% in key:", ok)

	ok = t.JSONPointer(got, `/e^f`, 3)
	fmt.Println("^ in key:", ok)

	ok = t.JSONPointer(got, `/g|h`, 4)
	fmt.Println("| in key:", ok)

	ok = t.JSONPointer(got, `/i\j`, 5)
	fmt.Println("Backslash in key:", ok)

	ok = t.JSONPointer(got, `/k"l`, 6)
	fmt.Println("Double-quote in key:", ok)

	ok = t.JSONPointer(got, `/ `, 7)
	fmt.Println("Space key:", ok)

	ok = t.JSONPointer(got, `/m~0n`, 8)
	fmt.Println("Tilde has to be escaped using `~0`:", ok)

}
Output:

Empty JSON pointer means all: true
Extract `foo` key: true
First item of `foo` key slice: true
Empty key: true
Slash has to be escaped using `~1`: true
% in key: true
^ in key: true
| in key: true
Backslash in key: true
Double-quote in key: true
Space key: true
Tilde has to be escaped using `~0`: true
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// Without json tags, encoding/json uses public fields name
	type Item struct {
		Name  string
		Value int64
		Next  *Item
	}

	got := Item{
		Name:  "first",
		Value: 1,
		Next: &Item{
			Name:  "second",
			Value: 2,
			Next: &Item{
				Name:  "third",
				Value: 3,
			},
		},
	}

	ok := t.JSONPointer(got, "/Next/Next/Name", "third")
	fmt.Println("3rd item name is `third`:", ok)

	ok = t.JSONPointer(got, "/Next/Next/Value", td.Gte(int64(3)))
	fmt.Println("3rd item value is greater or equal than 3:", ok)

	ok = t.JSONPointer(got, "/Next", td.JSONPointer("/Next",
		td.JSONPointer("/Value", td.Gte(int64(3)))))
	fmt.Println("3rd item value is still greater or equal than 3:", ok)

	ok = t.JSONPointer(got, "/Next/Next/Next/Name", td.Ignore())
	fmt.Println("4th item exists and has a name:", ok)

	// Struct comparison work with or without pointer: &Item{…} works too
	ok = t.JSONPointer(got, "/Next/Next", Item{
		Name:  "third",
		Value: 3,
	})
	fmt.Println("3rd item full comparison:", ok)

}
Output:

3rd item name is `third`: true
3rd item value is greater or equal than 3: true
3rd item value is still greater or equal than 3: true
4th item exists and has a name: false
3rd item full comparison: true

func (*T) Keys

func (t *T) Keys(got, val any, args ...any) bool

Keys is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// Keys tests keys in an ordered manner
	ok := t.Keys(got, []string{"bar", "foo", "zip"})
	fmt.Println("All sorted keys are found:", ok)

	// If the expected keys are not ordered, it fails
	ok = t.Keys(got, []string{"zip", "bar", "foo"})
	fmt.Println("All unsorted keys are found:", ok)

	// To circumvent that, one can use Bag operator
	ok = t.Keys(got, td.Bag("zip", "bar", "foo"))
	fmt.Println("All unsorted keys are found, with the help of Bag operator:", ok)

	// Check that each key is 3 bytes long
	ok = t.Keys(got, td.ArrayEach(td.Len(3)))
	fmt.Println("Each key is 3 bytes long:", ok)

}
Output:

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) Last added in v1.13.0

func (t *T) Last(got, filter, expectedValue any, args ...any) bool

Last is a shortcut for:

t.Cmp(got, td.Last(filter, expectedValue), args...)

See Last 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 (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := t.Last(got, td.Lt(0), -1)
	fmt.Println("last negative number is -1:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = t.Last(got, isEven, 2)
	fmt.Println("last even number is 2:", ok)

	ok = t.Last(got, isEven, td.Gt(0))
	fmt.Println("last even number is > 0:", ok)

	ok = t.Last(got, isEven, td.Code(isEven))
	fmt.Println("last even number is well even:", ok)

}
Output:

last negative number is -1: true
last even number is 2: true
last even number is > 0: true
last even number is well even: true
Example (Empty)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	ok := t.Last(([]int)(nil), td.Gt(0), td.Gt(0))
	fmt.Println("last in nil slice:", ok)

	ok = t.Last([]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("last in empty slice:", ok)

	ok = t.Last(&[]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("last in empty pointed slice:", ok)

	ok = t.Last([0]int{}, td.Gt(0), td.Gt(0))
	fmt.Println("last in empty array:", ok)

}
Output:

last in nil slice: false
last in empty slice: false
last in empty pointed slice: false
last in empty array: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      37,
		},
	}

	ok := t.Last(got, td.Smuggle("Age", td.Gt(30)), td.Smuggle("Fullname", "Alice Bingo"))
	fmt.Println("last person.Age > 30 → Alice:", ok)

	ok = t.Last(got, td.JSONPointer("/age", td.Gt(30)), td.SuperJSONOf(`{"fullname":"Alice Bingo"}`))
	fmt.Println("last person.Age > 30 → Alice, using JSON:", ok)

	ok = t.Last(got, td.JSONPointer("/age", td.Gt(30)), td.JSONPointer("/fullname", td.HasPrefix("Alice")))
	fmt.Println("first person.Age > 30 → Alice, using JSONPointer:", ok)

}
Output:

last person.Age > 30 → Alice: true
last person.Age > 30 → Alice, using JSON: true
first person.Age > 30 → Alice, using JSONPointer: true

func (*T) Len

func (t *T) Len(got, expectedLen any, args ...any) bool

Len is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (OperatorMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (OperatorSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true

func (*T) LogTrace added in v1.11.0

func (t *T) LogTrace(args ...any)

LogTrace uses t.TB.Log() to log a stack trace.

args... are optional and allow to prefix the trace by a message. If empty, this message defaults to "Stack trace:\n". If this message does not end with a "\n", one is automatically added. 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.

See also T.ErrorTrace and T.FatalTrace.

func (*T) Lt

func (t *T) Lt(got, maxExpectedValue any, args ...any) bool

Lt is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func (*T) Lte

func (t *T) Lte(got, maxExpectedValue any, args ...any) bool

Lte is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func (*T) Map

func (t *T) Map(got, model any, expectedEntries MapEntries, args ...any) bool

Map is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
true

func (*T) MapEach

func (t *T) MapEach(got, expectedValue any, args ...any) bool

MapEach is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func (*T) N

func (t *T) N(got, num, tolerance any, args ...any) bool

N is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true

func (*T) NaN

func (t *T) NaN(got any, args ...any) bool

NaN is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

}
Output:

float32(math.NaN()) is float32 not-a-number: true
float32(12) is float32 not-a-number: false
Example (Float64)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

	// math.NaN() is not-a-number: true
	// float64(12) is not-a-number: false
}
Output:

func (*T) Nil

func (t *T) Nil(got any, args ...any) bool

Nil is a shortcut for:

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

See 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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	var got fmt.Stringer // interface

	// nil value can be compared directly with nil, no need of Nil() here
	ok := t.Cmp(got, nil)
	fmt.Println(ok)

	// But it works with Nil() anyway
	ok = t.Nil(got)
	fmt.Println(ok)

	got = (*bytes.Buffer)(nil)

	// In the case of an interface containing a nil pointer, comparing
	// with nil fails, as the interface is not nil
	ok = t.Cmp(got, nil)
	fmt.Println(ok)

	// In this case Nil() succeed
	ok = t.Nil(got)
	fmt.Println(ok)

}
Output:

true
true
false
true

func (*T) None

func (t *T) None(got any, notExpectedValues []any, args ...any) bool

None is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := 18

	ok := t.None(got, []any{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, []any{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, []any{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, []any{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)
	}

}
Output:

true
false
false
9 → true
3 → false
8 → false
15 → false

func (*T) Not

func (t *T) Not(got, notExpected any, args ...any) bool

Not is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func (*T) NotAny

func (t *T) NotAny(got any, notExpectedItems []any, args ...any) bool

NotAny is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using notExpected... without copying it to a new
	// []any slice, then use td.Flatten!
	notExpected := []int{3, 6, 8, 41, 43}
	ok = t.NotAny(got, []any{td.Flatten(notExpected)},
		"checks %v contains no item listed in notExpected", got)
	fmt.Println(ok)

}
Output:

true
false
true

func (*T) NotEmpty

func (t *T) NotEmpty(got any, args ...any) bool

NotEmpty is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	ok := t.NotEmpty(nil) // fails, as nil is considered empty
	fmt.Println(ok)

	ok = t.NotEmpty("foobar")
	fmt.Println(ok)

	// Fails as 0 is a number, so not empty. Use NotZero() instead
	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{}) // succeeds, NotEmpty() is not NotZero()!
	fmt.Println(ok)

}
Output:

false
true
false
true
true
true
Example (Pointers)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type MySlice []int

	ok := t.NotEmpty(MySlice{12})
	fmt.Println(ok)

	ok = t.NotEmpty(&MySlice{12}) // Ptr() not needed
	fmt.Println(ok)

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

	// Works the same for array, map, channel and string

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

	ok = t.NotEmpty(&MyStruct{}) // fails, use NotZero() instead
	fmt.Println(ok)

}
Output:

true
true
true
false

func (*T) NotNaN

func (t *T) NotNaN(got any, args ...any) bool

NotNaN is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

}
Output:

float32(math.NaN()) is NOT float32 not-a-number: false
float32(12) is NOT float32 not-a-number: true
Example (Float64)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

	// math.NaN() is NOT not-a-number: false
	// float64(12) is NOT not-a-number: true
}
Output:

func (*T) NotNil

func (t *T) NotNil(got any, args ...any) bool

NotNil is a shortcut for:

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

See 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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// nil value can be compared directly with Not(nil), no need of NotNil() here
	ok := t.Cmp(got, td.Not(nil))
	fmt.Println(ok)

	// But it works with NotNil() anyway
	ok = t.NotNil(got)
	fmt.Println(ok)

	got = (*bytes.Buffer)(nil)

	// In the case of an interface containing a nil pointer, comparing
	// with Not(nil) succeeds, as the interface is not nil
	ok = t.Cmp(got, td.Not(nil))
	fmt.Println(ok)

	// In this case NotNil() fails
	ok = t.NotNil(got)
	fmt.Println(ok)

}
Output:

true
true
true
false

func (*T) NotZero

func (t *T) NotZero(got any, args ...any) bool

NotZero is a shortcut for:

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

See 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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	ok := t.NotZero(0) // fails
	fmt.Println(ok)

	ok = t.NotZero(float64(0)) // fails
	fmt.Println(ok)

	ok = t.NotZero(12)
	fmt.Println(ok)

	ok = t.NotZero((map[string]int)(nil)) // fails, as nil
	fmt.Println(ok)

	ok = t.NotZero(map[string]int{}) // succeeds, as not nil
	fmt.Println(ok)

	ok = t.NotZero(([]int)(nil)) // fails, as nil
	fmt.Println(ok)

	ok = t.NotZero([]int{}) // succeeds, as not nil
	fmt.Println(ok)

	ok = t.NotZero([3]int{}) // fails
	fmt.Println(ok)

	ok = t.NotZero([3]int{0, 1}) // succeeds, DATA[1] is not 0
	fmt.Println(ok)

	ok = t.NotZero(bytes.Buffer{}) // fails
	fmt.Println(ok)

	ok = t.NotZero(&bytes.Buffer{}) // succeeds, as pointer not nil
	fmt.Println(ok)

	ok = t.Cmp(&bytes.Buffer{}, td.Ptr(td.NotZero())) // fails as deref by Ptr()
	fmt.Println(ok)

}
Output:

false
false
true
false
true
false
true
false
true
false
true
false

func (*T) PPtr

func (t *T) PPtr(got, val any, args ...any) bool

PPtr is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func (*T) Parallel added in v1.10.0

func (t *T) Parallel()

Parallel marks this test as runnable in parallel with other parallel tests. If t.TB implements Parallel(), as *testing.T does, it is usually used to mark top-level tests and/or subtests as safe for parallel execution:

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

  t.Run("no error", func(t *td.T) {
    t.Parallel()

    // ...
  })

If t.TB does not implement Parallel(), this method is a no-op.

func (*T) Ptr

func (t *T) Ptr(got, val any, args ...any) bool

Ptr is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func (*T) Re

func (t *T) Re(got, reg, capture any, args ...any) bool

Re is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (Capture)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (Compiled)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (CompiledCapture)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (CompiledError)
package main

import (
	"errors"
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (CompiledStringer)
package main

import (
	"bytes"
	"fmt"
	"regexp"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foo bar")
	ok := t.Re(got, expected, nil, "checks value %s", got)
	fmt.Println(ok)

}
Output:

true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foo bar")
	ok := t.Re(got, "(zip|bar)$", nil, "checks value %s", got)
	fmt.Println(ok)

}
Output:

true

func (*T) ReAll

func (t *T) ReAll(got, reg, capture any, args ...any) bool

ReAll is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// Matches, but all catured groups do not match Set
	got = "foo BAR biz"
	ok = t.ReAll(got, `(\w+)`, td.Set("biz", "foo", "bar"),
		"checks value %s", got)
	fmt.Println(ok)

}
Output:

true
false
Example (CaptureComplex)
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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)

	// Matches, but 11 is not greater than 20
	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)

}
Output:

true
false
Example (CompiledCapture)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

	// Matches, but all catured groups do not match Set
	got = "foo BAR biz"
	ok = t.ReAll(got, expected, td.Set("biz", "foo", "bar"),
		"checks value %s", got)
	fmt.Println(ok)

}
Output:

true
false
Example (CompiledCaptureComplex)
package main

import (
	"fmt"
	"regexp"
	"strconv"
	"testing"

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

func main() {
	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)

	// Matches, but 11 is not greater than 20
	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)

}
Output:

true
false

func (*T) Recv added in v1.13.0

func (t *T) Recv(got, expectedValue any, timeout time.Duration, args ...any) bool

Recv is a shortcut for:

t.Cmp(got, td.Recv(expectedValue, timeout), args...)

See Recv for details.

Recv optional parameter timeout is here mandatory. 0 value should be passed to mimic its absence in original Recv 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 (Basic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := make(chan int, 3)

	ok := t.Recv(got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

	got <- 1
	got <- 2
	got <- 3
	close(got)

	ok = t.Recv(got, 1, 0)
	fmt.Println("1st receive is 1:", ok)

	ok = t.Cmp(got, td.All(
		td.Recv(2),
		td.Recv(td.Between(3, 4)),
		td.Recv(td.RecvClosed),
	))
	fmt.Println("next receives are 2, 3 then closed:", ok)

	ok = t.Recv(got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

}
Output:

nothing to receive: true
1st receive is 1: true
next receives are 2, 3 then closed: true
nothing to receive: false
Example (ChannelPointer)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := make(chan int, 3)

	ok := t.Recv(got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

	got <- 1
	got <- 2
	got <- 3
	close(got)

	ok = t.Recv(&got, 1, 0)
	fmt.Println("1st receive is 1:", ok)

	ok = t.Cmp(&got, td.All(
		td.Recv(2),
		td.Recv(td.Between(3, 4)),
		td.Recv(td.RecvClosed),
	))
	fmt.Println("next receives are 2, 3 then closed:", ok)

	ok = t.Recv(got, td.RecvNothing, 0)
	fmt.Println("nothing to receive:", ok)

}
Output:

nothing to receive: true
1st receive is 1: true
next receives are 2, 3 then closed: true
nothing to receive: false
Example (NilChannel)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	var ch chan int

	ok := t.Recv(ch, td.RecvNothing, 0)
	fmt.Println("nothing to receive from nil channel:", ok)

	ok = t.Recv(ch, 42, 0)
	fmt.Println("something to receive from nil channel:", ok)

	ok = t.Recv(ch, td.RecvClosed, 0)
	fmt.Println("is a nil channel closed:", ok)

}
Output:

nothing to receive from nil channel: true
something to receive from nil channel: false
is a nil channel closed: false
Example (WithTimeout)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := td.NewT(&testing.T{})

	got := make(chan int, 1)
	tick := make(chan struct{})

	go func() {
		// ①
		<-tick
		time.Sleep(100 * time.Millisecond)
		got <- 0

		// ②
		<-tick
		time.Sleep(100 * time.Millisecond)
		got <- 1

		// ③
		<-tick
		time.Sleep(100 * time.Millisecond)
		close(got)
	}()

	t.Recv(got, td.RecvNothing, 0)

	// ①
	tick <- struct{}{}
	ok := t.Recv(got, td.RecvNothing, 0)
	fmt.Println("① RecvNothing:", ok)
	ok = t.Recv(got, 0, 150*time.Millisecond)
	fmt.Println("① receive 0 w/150ms timeout:", ok)
	ok = t.Recv(got, td.RecvNothing, 0)
	fmt.Println("① RecvNothing:", ok)

	// ②
	tick <- struct{}{}
	ok = t.Recv(got, td.RecvNothing, 0)
	fmt.Println("② RecvNothing:", ok)
	ok = t.Recv(got, 1, 150*time.Millisecond)
	fmt.Println("② receive 1 w/150ms timeout:", ok)
	ok = t.Recv(got, td.RecvNothing, 0)
	fmt.Println("② RecvNothing:", ok)

	// ③
	tick <- struct{}{}
	ok = t.Recv(got, td.RecvNothing, 0)
	fmt.Println("③ RecvNothing:", ok)
	ok = t.Recv(got, td.RecvClosed, 150*time.Millisecond)
	fmt.Println("③ check closed w/150ms timeout:", ok)

}
Output:

① RecvNothing: true
① receive 0 w/150ms timeout: true
① RecvNothing: true
② RecvNothing: true
② receive 1 w/150ms timeout: true
② RecvNothing: true
③ RecvNothing: true
③ check closed w/150ms timeout: true

func (*T) Require added in v1.13.0

func (t *T) Require() *T

Require returns a new *T instance inheriting the t config but with FailureIsFatal flag set to true.

It returns a new instance of *T so does not alter the original t

It is a shortcut for:

t.FailureIsFatal(true)

See also T.FailureIsFatal and T.Assert.

func (*T) ResetAnchors

func (t *T) ResetAnchors()

ResetAnchors frees all operators anchored with T.Anchor method. Unless operators anchoring persistence has been enabled with T.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).

See also T.Anchor, T.AnchorsPersistTemporarily, T.DoAnchorsPersist, T.SetAnchorsPersist and AddAnchorableStructType.

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 is 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 added in v1.6.0

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.

See also T.RunAssertRequire.

func (*T) RunAssertRequire added in v1.7.0

func (t *T) RunAssertRequire(name string, f func(assert, 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.

See also T.Run.

func (*T) RunT deprecated

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 T.Run method. It is kept for compatibility.

func (*T) SStruct

func (t *T) SStruct(got, model any, expectedFields StructFields, args ...any) bool

SStruct is a shortcut for:

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

See SStruct for details.

SStruct optional parameter expectedFields is here mandatory. nil value should be passed to mimic its absence in original SStruct 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// NumChildren is not listed in expected fields so it must be zero
	ok := t.SStruct(got, Person{Name: "Foobar"}, td.StructFields{
		"Age": td.Between(40, 50),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	// Model can be empty
	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("Foobar has some children:", ok)

	// Works with pointers too
	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("Foobar has some children (using pointer):", ok)

	// Model does not need to be instanciated
	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("Foobar has some children (using nil model):", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar has some children: true
Foobar has some children (using pointer): true
Foobar has some children (using nil model): true
Example (Lazy_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := struct {
		name string
		age  int
	}{
		name: "Foobar",
		age:  42,
	}

	ok := t.SStruct(got, nil, td.StructFields{
		"name": "Foobar",
		"age":  td.Between(40, 45),
	})
	fmt.Println("Lazy model:", ok)

	ok = t.SStruct(got, nil, td.StructFields{
		"name": "Foobar",
		"zip":  666,
	})
	fmt.Println("Lazy model with unknown field:", ok)

}
Output:

Lazy model: true
Lazy model with unknown field: false
Example (Overwrite_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	ok := t.SStruct(got, Person{
		Name: "Foobar",
		Age:  53,
	}, td.StructFields{
		">Age":        td.Between(40, 50), // ">" to overwrite Age:53 in model
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	ok = t.SStruct(got, Person{
		Name: "Foobar",
		Age:  53,
	}, td.StructFields{
		"> Age":       td.Between(40, 50), // same, ">" can be followed by spaces
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar is between 40 & 50: true
Example (Patterns)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Firstname string
		Lastname  string
		Surname   string
		Nickname  string
		CreatedAt time.Time
		UpdatedAt time.Time
		DeletedAt *time.Time
		id        int64
		secret    string
	}

	now := time.Now()
	got := Person{
		Firstname: "Maxime",
		Lastname:  "Foo",
		Surname:   "Max",
		Nickname:  "max",
		CreatedAt: now,
		UpdatedAt: now,
		DeletedAt: nil, // not deleted yet
		id:        2345,
		secret:    "5ecr3T",
	}

	ok := t.SStruct(got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`: nil,
		`=  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`=~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
		`!  [A-Z]*`: td.Ignore(),        // private fields
	},
		"mix shell & regexp patterns")
	fmt.Println("Patterns match only remaining fields:", ok)

	ok = t.SStruct(got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`:   nil,
		`1 =  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`2 =~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
		`3 !~ ^[A-Z]`: td.Ignore(),        // private fields
	},
		"ordered patterns")
	fmt.Println("Ordered patterns match only remaining fields:", ok)

}
Output:

Patterns match only remaining fields: true
Ordered patterns match only remaining fields: true

func (*T) Set

func (t *T) Set(got any, expectedItems []any, args ...any) bool

Set is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// Matches as all items are present, ignoring duplicates
	ok := t.Set(got, []any{1, 2, 3, 5, 8},
		"checks all items are present, in any order")
	fmt.Println(ok)

	// Duplicates are ignored in a Set
	ok = t.Set(got, []any{1, 2, 2, 2, 2, 2, 3, 5, 8},
		"checks all items are present, in any order")
	fmt.Println(ok)

	// Tries its best to not raise an error when a value can be matched
	// by several Set entries
	ok = t.Set(got, []any{td.Between(1, 4), 3, td.Between(2, 10)},
		"checks all items are present, in any order")
	fmt.Println(ok)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 5, 8}
	ok = t.Set(got, []any{td.Flatten(expected)},
		"checks all expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
true
true
true

func (*T) SetAnchorsPersist

func (t *T) SetAnchorsPersist(persist bool)

SetAnchorsPersist allows to enable or disable anchors persistence.

See also T.Anchor, T.AnchorsPersistTemporarily, T.DoAnchorsPersist, T.ResetAnchors and AddAnchorableStructType.

func (*T) Shallow

func (t *T) Shallow(got, expectedPtr any, args ...any) bool

Shallow is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// Same contents, but not same pointer
	ok = t.Shallow(got, &MyStruct{Value: 12},
		"checks pointers only, not contents")
	fmt.Println(ok)

}
Output:

true
false
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

are ≠ but share the same area: true
are = but do not point to same area: false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

are ≠ but share the same area: true
are = but do not point to same area: false

func (*T) Slice

func (t *T) Slice(got, model any, expectedEntries ArrayEntries, args ...any) bool

Slice is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
true

func (*T) Smuggle

func (t *T) Smuggle(got, fn, expectedValue any, args ...any) bool

Smuggle is a shortcut for:

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

See 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)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// Automatically json.Unmarshal to compare
	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)

}
Output:

JSON contents is OK: true
Example (Cast)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// A string containing JSON
	got := `{ "foo": 123 }`

	// Automatically cast a string to a json.RawMessage so td.JSON can operate
	ok := t.Smuggle(got, json.RawMessage{}, td.JSON(`{"foo":123}`))
	fmt.Println("JSON contents in string is OK:", ok)

	// Automatically read from io.Reader to a json.RawMessage
	ok = t.Smuggle(bytes.NewReader([]byte(got)), json.RawMessage{}, td.JSON(`{"foo":123}`))
	fmt.Println("JSON contents just read is OK:", ok)

}
Output:

JSON contents in string is OK: true
JSON contents just read is OK: true
Example (Complex)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// Checks that end date is between 17th and 19th February both at 0h
	// for each of these durations in hours

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

		// Simplest way, but in case of Between() failure, error will be bound
		// to DATA<smuggled>, not very clear...
		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)

		// Name the computed value "ComputedEndDate" to render a Between() failure
		// more understandable, so error will be bound to DATA.ComputedEndDate
		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)
	}

}
Output:

false
false
true
true
true
true
Example (Convert)
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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) { //nolint: gocritic
		return strconv.Atoi(numStr)
	}, td.Between(120, 130),
		"checks that number in %#v is in [120 .. 130]")
	fmt.Println(ok)

	// Short version :)
	ok = t.Smuggle("123", strconv.Atoi, td.Between(120, 130),
		"checks that number in %#v is in [120 .. 130]")
	fmt.Println(ok)

}
Output:

true
true
true
true
true
Example (Field_path)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Body struct {
		Name  string
		Value any
	}
	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},
			},
		},
	}

	// Want to check whether Num is between 100 and 200?
	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)

	// Same, but automagically generated...
	ok = t.Smuggle(got, "Request.Body.Value.Num", td.Between(100, 200))
	fmt.Println("check Num using a fields-path:", ok)

	// And as Request is an anonymous field, can be simplified further
	// as it can be omitted
	ok = t.Smuggle(got, "Body.Value.Num", td.Between(100, 200))
	fmt.Println("check Num using an other fields-path:", ok)

	// Note that maps and array/slices are supported
	got.Request.Body.Value = map[string]any{
		"foo": []any{
			3: map[int]string{666: "bar"},
		},
	}
	ok = t.Smuggle(got, "Body.Value[foo][3][666]", "bar")
	fmt.Println("check fields-path including maps/slices:", ok)

}
Output:

check Num by hand: true
check Num using a fields-path: true
check Num using an other fields-path: true
check fields-path including maps/slices: true
Example (Interface)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// Do not check the struct itself, but its stringified form
	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)

}
Output:

stringified time.Time OK: true
stringified MyTime OK: false
Example (Lax)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// got is an int16 and Smuggle func input is an int64: it is OK
	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)

}
Output:

got int16(123) → smuggle via int64 → uint32(123): true

func (*T) String

func (t *T) String(got any, expected string, args ...any) bool

String is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func (*T) Struct

func (t *T) Struct(got, model any, expectedFields StructFields, args ...any) bool

Struct is a shortcut for:

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

See Struct for details.

Struct optional parameter expectedFields is here mandatory. nil value should be passed to mimic its absence in original Struct 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// As NumChildren is zero in Struct() call, it is not checked
	ok := t.Struct(got, Person{Name: "Foobar"}, td.StructFields{
		"Age": td.Between(40, 50),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	// Model can be empty
	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("Foobar has some children:", ok)

	// Works with pointers too
	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("Foobar has some children (using pointer):", ok)

	// Model does not need to be instanciated
	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("Foobar has some children (using nil model):", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar has some children: true
Foobar has some children (using pointer): true
Foobar has some children (using nil model): true
Example (Lazy_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	got := struct {
		name string
		age  int
	}{
		name: "Foobar",
		age:  42,
	}

	ok := t.Struct(got, nil, td.StructFields{
		"name": "Foobar",
		"age":  td.Between(40, 45),
	})
	fmt.Println("Lazy model:", ok)

	ok = t.Struct(got, nil, td.StructFields{
		"name": "Foobar",
		"zip":  666,
	})
	fmt.Println("Lazy model with unknown field:", ok)

}
Output:

Lazy model: true
Lazy model with unknown field: false
Example (Overwrite_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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",
		Age:  53,
	}, td.StructFields{
		">Age":        td.Between(40, 50), // ">" to overwrite Age:53 in model
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	ok = t.Struct(got, Person{
		Name: "Foobar",
		Age:  53,
	}, td.StructFields{
		"> Age":       td.Between(40, 50), // same, ">" can be followed by spaces
		"NumChildren": td.Gt(2),
	},
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar is between 40 & 50: true
Example (Patterns)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := td.NewT(&testing.T{})

	type Person struct {
		Firstname string
		Lastname  string
		Surname   string
		Nickname  string
		CreatedAt time.Time
		UpdatedAt time.Time
		DeletedAt *time.Time
	}

	now := time.Now()
	got := Person{
		Firstname: "Maxime",
		Lastname:  "Foo",
		Surname:   "Max",
		Nickname:  "max",
		CreatedAt: now,
		UpdatedAt: now,
		DeletedAt: nil, // not deleted yet
	}

	ok := t.Struct(got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`: nil,
		`=  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`=~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
	},
		"mix shell & regexp patterns")
	fmt.Println("Patterns match only remaining fields:", ok)

	ok = t.Struct(got, Person{Lastname: "Foo"}, td.StructFields{
		`DeletedAt`:  nil,
		`1 =  *name`: td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
		`2 =~ At\z`:  td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
	},
		"ordered patterns")
	fmt.Println("Ordered patterns match only remaining fields:", ok)

}
Output:

Patterns match only remaining fields: true
Ordered patterns match only remaining fields: true

func (*T) SubBagOf

func (t *T) SubBagOf(got any, expectedItems []any, args ...any) bool

SubBagOf is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	ok := t.SubBagOf(got, []any{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)

	// got contains one 8 too many
	ok = t.SubBagOf(got, []any{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, []any{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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 5, 9, 8}
	ok = t.SubBagOf(got, []any{td.Flatten(expected)},
		"checks at least all expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
false
true
true

func (*T) SubJSONOf

func (t *T) SubJSONOf(got, expectedJSON any, params []any, args ...any) bool

SubJSONOf is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	ok := t.SubJSONOf(got, filename, []any{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)

	// When the file is already open
	file, err := os.Open(filename)
	if err != nil {
		t.Fatal(err)
	}
	ok = t.SubJSONOf(got, file, []any{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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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}`, []any{42, "Bob Foobar", "male"})
	fmt.Println("check got with numeric placeholders without operators:", ok)

	ok = t.SubJSONOf(got, `{"age": $1, "fullname": $2, "gender": $3}`, []any{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"}`, []any{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}`, []any{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)

}
Output:

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, model any, expectedEntries MapEntries, args ...any) bool

SubMapOf is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func (*T) SubSetOf

func (t *T) SubSetOf(got any, expectedItems []any, args ...any) bool

SubSetOf is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

	// Tries its best to not raise an error when a value can be matched
	// by several SubSetOf entries
	ok = t.SubSetOf(got, []any{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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3, 4, 5, 6, 7, 8}
	ok = t.SubSetOf(got, []any{td.Flatten(expected)},
		"checks at least all expected items are present, in any order, ignoring duplicates")
	fmt.Println(ok)

}
Output:

true
true
true

func (*T) SuperBagOf

func (t *T) SuperBagOf(got any, expectedItems []any, args ...any) bool

SuperBagOf is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{8, 5, 8}
	ok = t.SuperBagOf(got, []any{td.Flatten(expected)},
		"checks the expected items are present, in any order")
	fmt.Println(ok)

}
Output:

true
true
true

func (*T) SuperJSONOf

func (t *T) SuperJSONOf(got, expectedJSON any, params []any, args ...any) bool

SuperJSONOf is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	ok := t.SuperJSONOf(got, filename, []any{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)

	// When the file is already open
	file, err := os.Open(filename)
	if err != nil {
		t.Fatal(err)
	}
	ok = t.SuperJSONOf(got, file, []any{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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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}`, []any{42, "Bob Foobar", "male"})
	fmt.Println("check got with numeric placeholders without operators:", ok)

	ok = t.SuperJSONOf(got, `{"age": $1, "fullname": $2, "gender": $3}`, []any{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"}`, []any{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}`, []any{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)

}
Output:

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, model any, expectedEntries MapEntries, args ...any) bool

SuperMapOf is a shortcut for:

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

See 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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 least all expected keys/values", got)
	fmt.Println(ok)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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 least 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 least all expected keys/values",
		got)
	fmt.Println(ok)

}
Output:

true
true

func (*T) SuperSetOf

func (t *T) SuperSetOf(got any, expectedItems []any, args ...any) bool

SuperSetOf is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

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

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

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	expected := []int{1, 2, 3}
	ok = t.SuperSetOf(got, []any{td.Flatten(expected)},
		"checks the expected items are present, in any order and ignoring duplicates")
	fmt.Println(ok)

}
Output:

true
true
true

func (*T) SuperSliceOf added in v1.10.0

func (t *T) SuperSliceOf(got, model any, expectedEntries ArrayEntries, args ...any) bool

SuperSliceOf is a shortcut for:

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

See SuperSliceOf 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	ok := t.SuperSliceOf(got, [4]int{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = t.SuperSliceOf(got, [4]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = t.SuperSliceOf(&got, &[4]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer:", ok)

	ok = t.SuperSliceOf(&got, (*[4]int)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of an array pointer: true
Only check items #0 & #3 of an array pointer, using nil model: true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	ok := t.SuperSliceOf(got, []int{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = t.SuperSliceOf(got, []int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = t.SuperSliceOf(&got, &[]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer:", ok)

	ok = t.SuperSliceOf(&got, (*[]int)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of a slice pointer: true
Only check items #0 & #3 of a slice pointer, using nil model: true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type MyArray [4]int

	got := MyArray{42, 58, 26, 666}

	ok := t.SuperSliceOf(got, MyArray{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks typed array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = t.SuperSliceOf(got, MyArray{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = t.SuperSliceOf(&got, &MyArray{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer:", ok)

	ok = t.SuperSliceOf(&got, (*MyArray)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of an array pointer: true
Only check items #0 & #3 of an array pointer, using nil model: true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	type MySlice []int

	got := MySlice{42, 58, 26, 666}

	ok := t.SuperSliceOf(got, MySlice{1: 58}, td.ArrayEntries{3: td.Gt(660)},
		"checks typed array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = t.SuperSliceOf(got, MySlice{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = t.SuperSliceOf(&got, &MySlice{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer:", ok)

	ok = t.SuperSliceOf(&got, (*MySlice)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)},
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of a slice pointer: true
Only check items #0 & #3 of a slice pointer, using nil model: true

func (*T) TestDeepInGotOK added in v1.13.0

func (t *T) TestDeepInGotOK(enable ...bool) *T

TestDeepInGotOK tells go-testdeep to not panic when a TestDeep operator is found on got side. By default it is forbidden because most of the time it is a mistake to compare (expected, got) instead of official (got, expected).

It returns a new instance of *T so does not alter the original t.

Note that t.TestDeepInGotOK() acts as t.TestDeepInGotOK(true).

func (*T) True

func (t *T) True(got any, args ...any) 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.

See also T.False.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func (*T) TruncTime

func (t *T) TruncTime(got, expectedTime any, trunc time.Duration, args ...any) bool

TruncTime is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	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")

	// Compare dates ignoring nanoseconds and monotonic parts
	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)

	// Compare dates ignoring time and so monotonic parts
	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)

	// Compare dates exactly but ignoring monotonic part
	expected = dateToTime("2018-05-01T12:45:53.123456789Z")
	ok = t.TruncTime(got, expected, 0,
		"checks date %v ignoring monotonic part", got)
	fmt.Println(ok)

}
Output:

true
true
true

func (*T) UseEqual

func (t *T) UseEqual(types ...any) *T

UseEqual tells go-testdeep to delegate the comparison of items whose type is one of types to their Equal() method.

The signature this method should be:

(A) Equal(B) bool

with B assignable to A.

See time.Time.Equal as an example of accepted Equal() method.

It always returns a new instance of *T so does not alter the original t.

t = t.UseEqual(time.Time{}, net.IP{})

types items can also be reflect.Type items. In this case, the target type is the one reflected by the reflect.Type.

t = t.UseEqual(reflect.TypeOf(time.Time{}), reflect.typeOf(net.IP{}))

As a special case, calling t.UseEqual() or t.UseEqual(true) returns an instance using the Equal() method globally, for all types owning an Equal() method. Other types fall back to the default comparison mechanism. t.UseEqual(false) returns an instance not using Equal() method anymore, except for types already recorded using a previous UseEqual call.

func (*T) Values

func (t *T) Values(got, val any, args ...any) bool

Values is a shortcut for:

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

See 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

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

	// Values tests values in an ordered manner
	ok := t.Values(got, []int{1, 2, 3})
	fmt.Println("All sorted values are found:", ok)

	// If the expected values are not ordered, it fails
	ok = t.Values(got, []int{3, 1, 2})
	fmt.Println("All unsorted values are found:", ok)

	// To circumvent that, one can use Bag operator
	ok = t.Values(got, td.Bag(3, 1, 2))
	fmt.Println("All unsorted values are found, with the help of Bag operator:", ok)

	// Check that each value is between 1 and 3
	ok = t.Values(got, td.ArrayEach(td.Between(1, 3)))
	fmt.Println("Each value is between 1 and 3:", ok)

}
Output:

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) WithCmpHooks added in v1.8.0

func (t *T) WithCmpHooks(fns ...any) *T

WithCmpHooks returns a new *T instance with new Cmp hooks recorded using functions passed in fns.

Each function in fns has to be a function with the following possible signatures:

func (got A, expected A) bool
func (got A, expected A) error

First arg is always got, and second is always expected.

A cannot be an interface. This restriction can be removed in the future, if really needed.

This function is called as soon as possible each time the type A is encountered for got while expected type is assignable to A.

When it returns a bool, false means A is not equal to B.

When it returns a non-nil error (meaning got ≠ expected), its content is used to tell the reason of the failure.

Cmp hooks are checked before UseEqual feature.

Cmp hooks are run just after Smuggle hooks.

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

  // Test reflect.Value contents instead of default field/field
  t = t.WithCmpHooks(func (got, expected reflect.Value) bool {
    return td.EqDeeply(got.Interface(), expected.Interface())
  })
  a, b := 1, 1
  t.Cmp(reflect.ValueOf(&a), reflect.ValueOf(&b)) // succeeds

  // Test reflect.Type correctly instead of default field/field
  t = t.WithCmpHooks(func (got, expected reflect.Type) bool {
    return got == expected
  })

  // Test time.Time via its Equal() method instead of default
  // field/field (note it bypasses the UseEqual flag)
  t = t.WithCmpHooks((time.Time).Equal)
  date, _ := time.Parse(time.RFC3339, "2020-09-08T22:13:54+02:00")
  t.Cmp(date, date.UTC()) // succeeds

  // Several hooks can be declared at once
  t = t.WithCmpHooks(
    func (got, expected reflect.Value) bool {
      return td.EqDeeply(got.Interface(), expected.Interface())
    },
    func (got, expected reflect.Type) bool {
      return got == expected
    },
    (time.Time).Equal,
  )
}

There is no way to add or remove hooks of an existing *T instance, only to create a new *T instance with this method or T.WithSmuggleHooks to add some.

WithCmpHooks calls t.Fatal if an item of fns is not a function or if its signature does not match the expected ones.

See also T.WithSmuggleHooks.

func (*T) WithSmuggleHooks added in v1.8.0

func (t *T) WithSmuggleHooks(fns ...any) *T

WithSmuggleHooks returns a new *T instance with new Smuggle hooks recorded using functions passed in fns.

Each function in fns has to be a function with the following possible signatures:

func (got A) B
func (got A) (B, error)

A cannot be an interface. This restriction can be removed in the future, if really needed.

B cannot be an interface. If you have a use case, we can talk about it.

This function is called as soon as possible each time the type A is encountered for got.

The B value returned replaces the got value for subsequent tests. Smuggle hooks are NOT run again for this returned value to avoid easy infinite loop recursion.

When it returns non-nil error (meaning something wrong happened during the conversion of A to B), it raises a global error and its content is used to tell the reason of the failure.

Smuggle hooks are run just before Cmp hooks.

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

  // Each encountered int is changed to a bool
  t = t.WithSmuggleHooks(func (got int) bool {
    return got != 0
  })
  t.Cmp(map[string]int{"ok": 1, "no": 0},
    map[string]bool{"ok", true, "no", false}) // succeeds

  // Each encountered string is converted to int
  t = t.WithSmuggleHooks(strconv.Atoi)
  t.Cmp("123", 123) // succeeds

  // Several hooks can be declared at once
  t = t.WithSmuggleHooks(
    func (got int) bool { return got != 0 },
    strconv.Atoi,
  )
}

There is no way to add or remove hooks of an existing *T instance, only create a new *T instance with this method or T.WithCmpHooks to add some.

WithSmuggleHooks calls t.Fatal if an item of fns is not a function or if its signature does not match the expected ones.

See also T.WithCmpHooks.

func (*T) Zero

func (t *T) Zero(got any, args ...any) bool

Zero is a shortcut for:

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

See 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
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := td.NewT(&testing.T{})

	ok := t.Zero(0)
	fmt.Println(ok)

	ok = t.Zero(float64(0))
	fmt.Println(ok)

	ok = t.Zero(12) // fails, as 12 is not 0 :)
	fmt.Println(ok)

	ok = t.Zero((map[string]int)(nil))
	fmt.Println(ok)

	ok = t.Zero(map[string]int{}) // fails, as not nil
	fmt.Println(ok)

	ok = t.Zero(([]int)(nil))
	fmt.Println(ok)

	ok = t.Zero([]int{}) // fails, as not nil
	fmt.Println(ok)

	ok = t.Zero([3]int{})
	fmt.Println(ok)

	ok = t.Zero([3]int{0, 1}) // fails, DATA[1] is not 0
	fmt.Println(ok)

	ok = t.Zero(bytes.Buffer{})
	fmt.Println(ok)

	ok = t.Zero(&bytes.Buffer{}) // fails, as pointer not nil
	fmt.Println(ok)

	ok = t.Cmp(&bytes.Buffer{}, td.Ptr(td.Zero())) // OK with the help of Ptr()
	fmt.Println(ok)

}
Output:

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
	// Error returns nil if the operator is operational, the
	// corresponding error otherwise.
	Error() error
	// 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 ...any) 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.

See also Any and None.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := "foo/bar"

	// Checks got string against:
	//   "o/b" regexp *AND* "bar" suffix *AND* exact "foo/bar" string
	ok := td.Cmp(t,
		got,
		td.All(td.Re("o/b"), td.HasSuffix("bar"), "foo/bar"),
		"checks value %s", got)
	fmt.Println(ok)

	// Checks got string against:
	//   "o/b" regexp *AND* "bar" suffix *AND* exact "fooX/Ybar" string
	ok = td.Cmp(t,
		got,
		td.All(td.Re("o/b"), td.HasSuffix("bar"), "fooX/Ybar"),
		"checks value %s", got)
	fmt.Println(ok)

	// When some operators or values have to be reused and mixed between
	// several calls, Flatten can be used to avoid boring and
	// inefficient []any copies:
	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)

}
Output:

true
false
true

func Any

func Any(expectedValues ...any) 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.

See also All and None.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := "foo/bar"

	// Checks got string against:
	//   "zip" regexp *OR* "bar" suffix
	ok := td.Cmp(t, got, td.Any(td.Re("zip"), td.HasSuffix("bar")),
		"checks value %s", got)
	fmt.Println(ok)

	// Checks got string against:
	//   "zip" regexp *OR* "foo" suffix
	ok = td.Cmp(t, got, td.Any(td.Re("zip"), td.HasSuffix("foo")),
		"checks value %s", got)
	fmt.Println(ok)

	// When some operators or values have to be reused and mixed between
	// several calls, Flatten can be used to avoid boring and
	// inefficient []any copies:
	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)

}
Output:

true
false
true

func Array

func Array(model any, expectedEntries ArrayEntries) TestDeep

Array operator compares the contents of an array or a pointer on an array against the values of model and the values of expectedEntries. Entries with zero values of model are ignored if the same entry is present in expectedEntries, otherwise they are taken into account. An entry cannot be present in both model and expectedEntries, except if it is a zero-value in model. At the end, all entries are checked. To check only some entries of an array, see SuperSliceOf operator.

model must be the same type as compared data.

expectedEntries can be nil, if no zero entries are expected and no TestDeep operators 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.

See also Slice and SuperSliceOf.

Example (Array)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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("Simple array:", ok)

	ok = td.Cmp(t, &got,
		td.Array(&[3]int{42}, td.ArrayEntries{1: 58, 2: td.Ignore()}),
		"checks array %v", got)
	fmt.Println("Array pointer:", ok)

	ok = td.Cmp(t, &got,
		td.Array((*[3]int)(nil), td.ArrayEntries{0: 42, 1: 58, 2: td.Ignore()}),
		"checks array %v", got)
	fmt.Println("Array pointer, nil model:", ok)

}
Output:

Simple array: true
Array pointer: true
Array pointer, nil model: true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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("Typed array:", 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("Pointer on a typed array:", 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("Pointer on a typed array, empty model:", 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("Pointer on a typed array, nil model:", ok)

}
Output:

Typed array: true
Pointer on a typed array: true
Pointer on a typed array, empty model: true
Pointer on a typed array, nil model: true

func ArrayEach

func ArrayEach(expectedValue any) 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func Bag

func Bag(expectedItems ...any) 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-[]any 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))

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.

See also SubBagOf, SuperBagOf and Set.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// Matches as all items are present
	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)

	// Does not match as got contains 2 times 1 and 8, and these
	// duplicates are not expected
	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}

	// Duplicates of 1 and 8 are expected but not present in got
	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)

	// Matches as all items are present
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

true
false
false
true
true

func Between

func Between(from, to any, bounds ...BoundsKind) TestDeep

Between operator checks that data is between from and to. from and to can be any numeric, string, time.Time (or assignable) value or implement at least one of the two following methods:

func (a T) Less(b T) bool   // returns true if a < b
func (a T) Compare(b T) int // returns -1 if a < b, 1 if a > b, 0 if a == b

from and to must be the same type as the compared value, except if BeLax config flag is true. time.Duration type is accepted as to when from is time.Time or convertible. bounds allows to specify whether bounds are included or not:

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
tc.Cmp(t,                                       // succeeds
  netip.MustParse("127.0.0.1"),
  td.Between(netip.MustParse("127.0.0.0"), netip.MustParse("127.255.255.255")))

TypeBehind method returns the reflect.Type of from.

Example (Int)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := 156

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

	// BoundsInIn is implicit
	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)

}
Output:

true
true
false
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := "abc"

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

	// BoundsInIn is implicit
	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)

}
Output:

true
true
false
true
false
Example (Time)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	before := time.Now()
	occurredAt := time.Now()
	after := time.Now()

	ok := td.Cmp(t, occurredAt, td.Between(before, after))
	fmt.Println("It occurred between before and after:", ok)

	type MyTime time.Time
	ok = td.Cmp(t, MyTime(occurredAt), td.Between(MyTime(before), MyTime(after)))
	fmt.Println("Same for convertible MyTime type:", ok)

	ok = td.Cmp(t, MyTime(occurredAt), td.Between(before, after))
	fmt.Println("MyTime vs time.Time:", ok)

	ok = td.Cmp(t, occurredAt, td.Between(before, 10*time.Second))
	fmt.Println("Using a time.Duration as TO:", ok)

	ok = td.Cmp(t, MyTime(occurredAt), td.Between(MyTime(before), 10*time.Second))
	fmt.Println("Using MyTime as FROM and time.Duration as TO:", ok)

}
Output:

It occurred between before and after: true
Same for convertible MyTime type: true
MyTime vs time.Time: false
Using a time.Duration as TO: true
Using MyTime as FROM and time.Duration as TO: true

func Cap

func Cap(expectedCap any) 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)))

See also Len.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (Operator)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func Catch

func Catch(target, expectedValue any) 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)
}

TypeBehind method returns the reflect.Type of expectedValue, except if expectedValue is a TestDeep operator. In this case, it delegates TypeBehind() to the operator, but if nil is returned by this call, the dereferenced reflect.Type of target is returned.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

check got age+fullname: true
caught age: 42

func Code

func Code(fn any) 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.

But in some cases it can be better to handle yourself the comparison than to chain TestDeep operators. In this case, fn can be a function receiving one or two *T as first parameters and returning no values.

When fn expects one *T parameter, it is directly derived from the testing.TB instance passed originally to Cmp (or its derivatives) using NewT:

td.Cmp(t, httpRequest, td.Code(func(t *td.T, r *http.Request) {
  token, err := DecodeToken(r.Header.Get("X-Token-1"))
  if t.CmpNoError(err) {
    t.True(token.OK())
  }
}))

When fn expects two *T parameters, they are directly derived from the testing.TB instance passed originally to Cmp (or its derivatives) using AssertRequire:

td.Cmp(t, httpRequest, td.Code(func(assert, require *td.T, r *http.Request) {
  token, err := DecodeToken(r.Header.Get("X-Token-1"))
  require.CmpNoError(err)
  assert.True(token.OK())
}))

Note that these forms do not work when there is no initial testing.TB instance, like when using EqDeeplyError or EqDeeply functions, or when the Code operator is called behind the following operators, as they just check if a match occurs without raising an error: Any, Bag, Contains, ContainsKey, None, Not, NotAny, Set, SubBagOf, SubSetOf, SuperBagOf and SuperSetOf.

RootName is inherited but not the current path, but it can be recovered if needed:

got := map[string]int{"foo": 123}
td.NewT(t).
  RootName("PIPO").
  Cmp(got, td.Map(map[string]int{}, td.MapEntries{
    "foo": td.Code(func(t *td.T, n int) {
      t.Cmp(n, 124)                                   // inherit only RootName
      t.RootName(t.Config.OriginalPath()).Cmp(n, 125) // recover current path
      t.RootName("").Cmp(n, 126)                      // undo RootName inheritance
    }),
  }))

produces the following errors:

--- FAIL: TestCodeCustom (0.00s)
    td_code_test.go:339: Failed test
        PIPO: values differ             ← inherit only RootName
               got: 123
          expected: 124
    td_code_test.go:338: Failed test
        PIPO["foo"]: values differ      ← recover current path
               got: 123
          expected: 125
    td_code_test.go:342: Failed test
        DATA: values differ             ← undo RootName inheritance
               got: 123
          expected: 126

TypeBehind method returns the reflect.Type of last parameter of fn.

Example
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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)

	// Same with failure reason
	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)

	// Same with failure reason thanks to error
	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)

}
Output:

true
true
true
Example (Custom)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := 123

	ok := td.Cmp(t, got, td.Code(func(t *td.T, num int) {
		t.Cmp(num, 123)
	}))
	fmt.Println("with one *td.T:", ok)

	ok = td.Cmp(t, got, td.Code(func(assert, require *td.T, num int) {
		assert.Cmp(num, 123)
		require.Cmp(num, 123)
	}))
	fmt.Println("with assert & require *td.T:", ok)

}
Output:

with one *td.T: true
with assert & require *td.T: true

func Contains

func Contains(expectedValue any) 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

See also ContainsKey.

Example (ArraySlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

contains `oob` string: true
contains 'b' rune: true
contains 'a' byte: true
contains at least one character ['n' .. 'p']: true
Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

map contains value 22: true
map contains at least one value in [20 .. 25]: true
Example (Nil)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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) // types differ: *byte ≠ *int

}
Output:

array contains untyped nil: true
array contains *int nil: true
array contains Nil(): true
array contains *byte nil: false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	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)

}
Output:

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 any) 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)

See also Contains.

Example
package main

import (
	"fmt"
	"strings"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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) // types differ: *byte ≠ *int

}
Output:

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 added in v1.4.0

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
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	cmpNow := func(expected td.TestDeep) bool {
		time.Sleep(time.Microsecond) // imagine a DB insert returning a CreatedAt
		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)

}
Output:

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.Cmp(t, nil, td.Empty()) // special case: nil is considered empty
	fmt.Println(ok)

	// fails, typed nil is not empty (expect for channel, map, slice or
	// pointers on array, channel, map slice and strings)
	ok = td.Cmp(t, (*int)(nil), td.Empty())
	fmt.Println(ok)

	ok = td.Cmp(t, "", td.Empty())
	fmt.Println(ok)

	// Fails as 0 is a number, so not empty. Use Zero() instead
	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()) // fails, as not empty
	fmt.Println(ok)

	ok = td.Cmp(t, [3]int{}, td.Empty()) // fails, Empty() is not Zero()!
	fmt.Println(ok)

}
Output:

true
false
true
false
true
true
true
true
false
false
Example (Pointers)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type MySlice []int

	ok := td.Cmp(t, MySlice{}, td.Empty()) // Ptr() not needed
	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)

	// Works the same for array, map, channel and string

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

	ok = td.Cmp(t, &MyStruct{}, td.Empty()) // fails, use Zero() instead
	fmt.Println(ok)

}
Output:

true
true
true
false

func ErrorIs added in v1.13.0

func ErrorIs(expectedError any) TestDeep

ErrorIs is a smuggler operator. It reports whether any error in an error's chain matches expectedError.

_, err := os.Open("/unknown/file")
td.Cmp(t, err, os.ErrNotExist)             // fails
td.Cmp(t, err, td.ErrorIs(os.ErrNotExist)) // succeeds

err1 := fmt.Errorf("failure1")
err2 := fmt.Errorf("failure2: %w", err1)
err3 := fmt.Errorf("failure3: %w", err2)
err := fmt.Errorf("failure4: %w", err3)
td.Cmp(t, err, td.ErrorIs(err))  // succeeds
td.Cmp(t, err, td.ErrorIs(err1)) // succeeds
td.Cmp(t, err1, td.ErrorIs(err)) // fails

var cerr myError
td.Cmp(t, err, td.ErrorIs(td.Catch(&cerr, td.String("my error..."))))

td.Cmp(t, err, td.ErrorIs(td.All(
  td.Isa(myError{}),
  td.String("my error..."),
)))

Behind the scene it uses errors.Is function if expectedError is an [error] and errors.As function if expectedError is a TestDeep operator.

Note that like errors.Is, expectedError can be nil: in this case the comparison succeeds only when got is nil too.

See also CmpError and CmpNoError.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	err1 := fmt.Errorf("failure1")
	err2 := fmt.Errorf("failure2: %w", err1)
	err3 := fmt.Errorf("failure3: %w", err2)
	err := fmt.Errorf("failure4: %w", err3)

	ok := td.Cmp(t, err, td.ErrorIs(err))
	fmt.Println("error is itself:", ok)

	ok = td.Cmp(t, err, td.ErrorIs(err1))
	fmt.Println("error is also err1:", ok)

	ok = td.Cmp(t, err1, td.ErrorIs(err))
	fmt.Println("err1 is err:", ok)

}
Output:

error is itself: true
error is also err1: true
err1 is err: false

func First added in v1.13.0

func First(filter, expectedValue any) TestDeep

First is a smuggler operator. It takes an array, a slice or a pointer on array/slice. For each item it applies filter, a TestDeep operator or a function returning a bool. It takes the first item for which the filter matched and compares it to expectedValue. The filter matches when it is a:

  • TestDeep operator and it matches for the item;
  • function receiving the item and it returns true.

expectedValue can of course be a TestDeep operator.

got := []int{-3, -2, -1, 0, 1, 2, 3}
td.Cmp(t, got, td.First(td.Gt(0), 1))                                    // succeeds
td.Cmp(t, got, td.First(func(x int) bool { return x%2 == 0 }, -2))       // succeeds
td.Cmp(t, got, td.First(func(x int) bool { return x%2 == 0 }, td.Lt(0))) // succeeds

If the input is empty (and/or nil for a slice), an "item not found" error is raised before comparing to expectedValue.

var got []int
td.Cmp(t, got, td.First(td.Gt(0), td.Gt(0)))      // fails
td.Cmp(t, []int{}, td.First(td.Gt(0), td.Gt(0)))  // fails
td.Cmp(t, [0]int{}, td.First(td.Gt(0), td.Gt(0))) // fails

See also Last and Grep.

Example (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := td.Cmp(t, got, td.First(td.Gt(0), 1))
	fmt.Println("first positive number is 1:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = td.Cmp(t, got, td.First(isEven, -2))
	fmt.Println("first even number is -2:", ok)

	ok = td.Cmp(t, got, td.First(isEven, td.Lt(0)))
	fmt.Println("first even number is < 0:", ok)

	ok = td.Cmp(t, got, td.First(isEven, td.Code(isEven)))
	fmt.Println("first even number is well even:", ok)

}
Output:

first positive number is 1: true
first even number is -2: true
first even number is < 0: true
first even number is well even: true
Example (Empty)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.Cmp(t, ([]int)(nil), td.First(td.Gt(0), td.Gt(0)))
	fmt.Println("first in nil slice:", ok)

	ok = td.Cmp(t, []int{}, td.First(td.Gt(0), td.Gt(0)))
	fmt.Println("first in empty slice:", ok)

	ok = td.Cmp(t, &[]int{}, td.First(td.Gt(0), td.Gt(0)))
	fmt.Println("first in empty pointed slice:", ok)

	ok = td.Cmp(t, [0]int{}, td.First(td.Gt(0), td.Gt(0)))
	fmt.Println("first in empty array:", ok)

}
Output:

first in nil slice: false
first in empty slice: false
first in empty pointed slice: false
first in empty array: false
Example (Json)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := map[string]any{
		"values": []int{1, 2, 3, 4},
	}
	ok := td.Cmp(t, got, td.JSON(`{"values": First(Gt(2), 3)}`))
	fmt.Println("first number > 2:", ok)

	got = map[string]any{
		"persons": []map[string]any{
			{"id": 1, "name": "Joe"},
			{"id": 2, "name": "Bob"},
			{"id": 3, "name": "Alice"},
			{"id": 4, "name": "Brian"},
			{"id": 5, "name": "Britt"},
		},
	}
	ok = td.Cmp(t, got, td.JSON(`
{
  "persons": First(JSONPointer("/name", "Brian"), {"id": 4, "name": "Brian"})
}`))
	fmt.Println(`is "Brian" content OK:`, ok)

	ok = td.Cmp(t, got, td.JSON(`
{
  "persons": First(JSONPointer("/name", "Brian"), JSONPointer("/id", 4))
}`))
	fmt.Println(`ID of "Brian" is 4:`, ok)

}
Output:

first number > 2: true
is "Brian" content OK: true
ID of "Brian" is 4: true
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      37,
		},
	}

	ok := td.Cmp(t, got, td.First(
		td.Smuggle("Age", td.Gt(30)),
		td.Smuggle("Fullname", "Bob Foobar")))
	fmt.Println("first person.Age > 30 → Bob:", ok)

	ok = td.Cmp(t, got, td.First(
		td.JSONPointer("/age", td.Gt(30)),
		td.SuperJSONOf(`{"fullname":"Bob Foobar"}`)))
	fmt.Println("first person.Age > 30 → Bob, using JSON:", ok)

	ok = td.Cmp(t, got, td.First(
		td.JSONPointer("/age", td.Gt(30)),
		td.JSONPointer("/fullname", td.HasPrefix("Bob"))))
	fmt.Println("first person.Age > 30 → Bob, using JSONPointer:", ok)

}
Output:

first person.Age > 30 → Bob: true
first person.Age > 30 → Bob, using JSON: true
first person.Age > 30 → Bob, using JSONPointer: true

func Grep added in v1.13.0

func Grep(filter, expectedValue any) TestDeep

Grep is a smuggler operator. It takes an array, a slice or a pointer on array/slice. For each item it applies filter, a TestDeep operator or a function returning a bool, and produces a slice consisting of those items for which the filter matched and compares it to expectedValue. The filter matches when it is a:

  • TestDeep operator and it matches for the item;
  • function receiving the item and it returns true.

expectedValue can be a TestDeep operator or a slice (but never an array nor a pointer on a slice/array nor any other kind).

got := []int{-3, -2, -1, 0, 1, 2, 3}
td.Cmp(t, got, td.Grep(td.Gt(0), []int{1, 2, 3})) // succeeds
td.Cmp(t, got, td.Grep(
  func(x int) bool { return x%2 == 0 },
  []int{-2, 0, 2})) // succeeds
td.Cmp(t, got, td.Grep(
  func(x int) bool { return x%2 == 0 },
  td.Set(0, 2, -2))) // succeeds

If Grep receives a nil slice or a pointer on a nil slice, it always returns a nil slice:

var got []int
td.Cmp(t, got, td.Grep(td.Gt(0), ([]int)(nil))) // succeeds
td.Cmp(t, got, td.Grep(td.Gt(0), td.Nil()))     // succeeds
td.Cmp(t, got, td.Grep(td.Gt(0), []int{}))      // fails

See also First, Last and Flatten.

Example (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := td.Cmp(t, got, td.Grep(td.Gt(0), []int{1, 2, 3}))
	fmt.Println("check positive numbers:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = td.Cmp(t, got, td.Grep(isEven, []int{-2, 0, 2}))
	fmt.Println("even numbers are -2, 0 and 2:", ok)

	ok = td.Cmp(t, got, td.Grep(isEven, td.Set(0, 2, -2)))
	fmt.Println("even numbers are also 0, 2 and -2:", ok)

	ok = td.Cmp(t, got, td.Grep(isEven, td.ArrayEach(td.Code(isEven))))
	fmt.Println("even numbers are each even:", ok)

}
Output:

check positive numbers: true
even numbers are -2, 0 and 2: true
even numbers are also 0, 2 and -2: true
even numbers are each even: true
Example (Json)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := map[string]any{
		"values": []int{1, 2, 3, 4},
	}
	ok := td.Cmp(t, got, td.JSON(`{"values": Grep(Gt(2), [3, 4])}`))
	fmt.Println("grep a number > 2:", ok)

	got = map[string]any{
		"persons": []map[string]any{
			{"id": 1, "name": "Joe"},
			{"id": 2, "name": "Bob"},
			{"id": 3, "name": "Alice"},
			{"id": 4, "name": "Brian"},
			{"id": 5, "name": "Britt"},
		},
	}
	ok = td.Cmp(t, got, td.JSON(`
{
  "persons": Grep(JSONPointer("/name", HasPrefix("Br")), [
    {"id": 4, "name": "Brian"},
    {"id": 5, "name": "Britt"},
  ])
}`))
	fmt.Println(`grep "Br" prefix:`, ok)

}
Output:

grep a number > 2: true
grep "Br" prefix: true
Example (Nil)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	var got []int
	ok := td.Cmp(t, got, td.Grep(td.Gt(0), ([]int)(nil)))
	fmt.Println("typed []int nil:", ok)

	ok = td.Cmp(t, got, td.Grep(td.Gt(0), ([]string)(nil)))
	fmt.Println("typed []string nil:", ok)

	ok = td.Cmp(t, got, td.Grep(td.Gt(0), td.Nil()))
	fmt.Println("td.Nil:", ok)

	ok = td.Cmp(t, got, td.Grep(td.Gt(0), []int{}))
	fmt.Println("empty non-nil slice:", ok)

}
Output:

typed []int nil: true
typed []string nil: false
td.Nil: true
empty non-nil slice: false
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      27,
		},
	}

	ok := td.Cmp(t, got, td.Grep(
		td.Smuggle("Age", td.Gt(30)),
		td.All(
			td.Len(1),
			td.ArrayEach(td.Smuggle("Fullname", "Bob Foobar")),
		)))
	fmt.Println("person.Age > 30 → only Bob:", ok)

	ok = td.Cmp(t, got, td.Grep(
		td.JSONPointer("/age", td.Gt(30)),
		td.JSON(`[ SuperMapOf({"fullname":"Bob Foobar"}) ]`)))
	fmt.Println("person.Age > 30 → only Bob, using JSON:", ok)

}
Output:

person.Age > 30 → only Bob: true
person.Age > 30 → only Bob, using JSON: true

func Gt

func Gt(minExpectedValue any) TestDeep

Gt operator checks that data is greater than minExpectedValue. minExpectedValue can be any numeric, string, time.Time (or assignable) value or implements at least one of the two following methods:

func (a T) Less(b T) bool   // returns true if a < b
func (a T) Compare(b T) int // returns -1 if a < b, 1 if a > b, 0 if a == b

minExpectedValue must be the same type as the compared value, except if BeLax config flag is true.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func Gte

func Gte(minExpectedValue any) TestDeep

Gte operator checks that data is greater or equal than minExpectedValue. minExpectedValue can be any numeric, string, time.Time (or assignable) value or implements at least one of the two following methods:

func (a T) Less(b T) bool   // returns true if a < b
func (a T) Compare(b T) int // returns -1 if a < b, 1 if a > b, 0 if a == b

minExpectedValue must be the same type as the compared value, except if BeLax config flag is true.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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

See also Contains, HasSuffix, Re, ReAll and String.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

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

See also Contains, HasPrefix, Re, ReAll and String.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

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 (if you don't want to use SuperSliceOf). Or comparing a struct with SStruct and wanting to ignore some fields:

td.Cmp(t, got, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    Age:      td.Between(40, 45),
    Children: td.Ignore(),
  }),
)
Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.Cmp(t, []int{1, 2, 3},
		td.Slice([]int{}, td.ArrayEntries{
			0: 1,
			1: td.Ignore(), // do not care about this entry
			2: 3,
		}))
	fmt.Println(ok)

}
Output:

true

func Isa

func Isa(model any) 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (Interface)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	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)

	// As nil, is passed below, it is not an interface but nil… So it
	// does not match
	errGot = nil

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

	// BUT if its address is passed, now it is OK as the types match
	ok = td.Cmp(t, &errGot, td.Isa((*error)(nil)),
		"checks &errGot is a *error or implements error interface")
	fmt.Println(ok)

}
Output:

true
true
false
true

func JSON

func JSON(expectedJSON any, params ...any) 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 os.ReadFile before unmarshaling)
  • []byte containing JSON data
  • encoding/json.RawMessage containing JSON data
  • io.Reader stream containing JSON data (is io.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.

JSON does its best to convert back the JSON corresponding to a placeholder to the type of the placeholder or, if the placeholder is an operator, to the type behind the operator. Allowing to do things like:

td.Cmp(t, gotValue, td.JSON(`{"foo":$1}`, []int{1, 2, 3, 4}))
td.Cmp(t, gotValue,
  td.JSON(`{"foo":$1}`, []any{1, 2, td.Between(2, 4), 4}))
td.Cmp(t, gotValue, td.JSON(`{"foo":$1}`, td.Between(27, 32)))

Of course, it does this conversion only if the expected type can be guessed. In the case the conversion cannot occur, data is compared as is, in its freshly unmarshaled JSON form (so as bool, float64, string, []any, map[string]any or simply nil).

Note expectedJSON can be a []byte, an encoding/json.RawMessage, a JSON filename or a 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 */.

Other JSON divergences:

  • ',' can precede a '}' or a ']' (as in go);
  • strings can contain non-escaped \n, \r and \t;
  • raw strings are accepted (r{raw}, r!raw!, …), see below;
  • int_lit & float_lit numbers as defined in go spec are accepted;
  • numbers can be prefixed by '+'.

Most operators can be directly embedded in JSON without requiring any placeholder. If an operators does not take any parameter, the parenthesis can be omitted.

td.Cmp(t, gotValue,
  td.JSON(`
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty, // () are optional when no parameters
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`))

Placeholders can be used anywhere, even in operators parameters as in:

td.Cmp(t, gotValue, td.JSON(`{"fullname": HasPrefix($1)}`, "Zip"))

A few notes about operators embedding:

It is also possible to embed operators in JSON strings. This way, the JSON specification can be fulfilled. To avoid collision with possible strings, just prefix the first operator name with "$^". The previous example becomes:

td.Cmp(t, gotValue,
  td.JSON(`
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    \"address\": NotEmpty, // () are optional when no parameters
    \"car\":     Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these
  })"
}`))

As you can see, in this case, strings in strings have to be escaped. Fortunately, newlines are accepted, but unfortunately they are forbidden by JSON specification. To avoid too much escaping, raw strings are accepted. A raw string is a "r" followed by a delimiter, the corresponding delimiter closes the string. The following raw strings are all the same as "foo\\bar(\"zip\")!":

  • r'foo\bar"zip"!'
  • r,foo\bar"zip"!,
  • r%foo\bar"zip"!%
  • r(foo\bar("zip")!)
  • r{foo\bar("zip")!}
  • r[foo\bar("zip")!]
  • r<foo\bar("zip")!>

So non-bracketing delimiters use the same character before and after, but the 4 sorts of ASCII brackets (round, angle, square, curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot be escaped.

With raw strings, the previous example becomes:

td.Cmp(t, gotValue,
  td.JSON(`
{
  "fullname": "$^HasPrefix(r<Foo>)",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    r<address>: NotEmpty, // () are optional when no parameters
    r<car>:     Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these
  })"
}`))

Note that raw strings are accepted anywhere, not only in original JSON strings.

To be complete, $^ can prefix an operator even outside a string. This is accepted for compatibility purpose as the first operator embedding feature used this way to embed some operators.

So the following calls are all equivalent:

td.Cmp(t, gotValue, td.JSON(`{"id": $1}`, td.NotZero()))
td.Cmp(t, gotValue, td.JSON(`{"id": NotZero}`))
td.Cmp(t, gotValue, td.JSON(`{"id": NotZero()}`))
td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero}`))
td.Cmp(t, gotValue, td.JSON(`{"id": $^NotZero()}`))
td.Cmp(t, gotValue, td.JSON(`{"id": "$^NotZero"}`))
td.Cmp(t, gotValue, td.JSON(`{"id": "$^NotZero()"}`))

As for placeholders, there is no differences between $^NotZero and "$^NotZero".

Tip: when an io.Reader is expected to contain JSON data, it cannot be tested directly, but using the Smuggle operator simply solves the problem:

var body io.Reader
// …
td.Cmp(t, body, td.Smuggle(json.RawMessage{}, td.JSON(`{"foo":1}`)))
// or equally
td.Cmp(t, body, td.Smuggle(json.RawMessage(nil), td.JSON(`{"foo":1}`)))

Smuggle reads from body into an encoding/json.RawMessage then this buffer is unmarshaled by JSON operator before the comparison.

TypeBehind method returns the reflect.Type of the expectedJSON once JSON unmarshaled. So it can be bool, string, float64, []any, map[string]any or any in case expectedJSON is "null".

See also JSONPointer, SubJSONOf and SuperJSONOf.

Example (Basic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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 (Embedding)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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": NotZero(), "fullname": NotEmpty()}`))
	fmt.Println("check got with simple operators:", ok)

	ok = td.Cmp(t, got, td.JSON(`{"age": $^NotZero, "fullname": $^NotEmpty}`))
	fmt.Println("check got with operator shortcuts:", ok)

	ok = td.Cmp(t, got, td.JSON(`
{
  "age":      Between(40, 42, "]]"), // in ]40; 42]
  "fullname": All(
    HasPrefix("Bob"),
    HasSuffix("bar")  // ← comma is optional here
  )
}`))
	fmt.Println("check got with complex operators:", ok)

	ok = td.Cmp(t, got, td.JSON(`
{
  "age":      Between(40, 42, "]["), // in ]40; 42[ → 42 excluded
  "fullname": All(
    HasPrefix("Bob"),
    HasSuffix("bar"),
  )
}`))
	fmt.Println("check got with complex operators:", ok)

	ok = td.Cmp(t, got, td.JSON(`
{
  "age":      Between($1, $2, $3), // in ]40; 42]
  "fullname": All(
    HasPrefix($4),
    HasSuffix("bar")  // ← comma is optional here
  )
}`,
		40, 42, td.BoundsOutIn,
		"Bob"))
	fmt.Println("check got with complex operators, w/placeholder args:", ok)

}
Output:

check got with simple operators: true
check got with operator shortcuts: true
check got with complex operators: true
check got with complex operators: false
check got with complex operators, w/placeholder args: true
Example (File)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	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)

	// When the file is already open
	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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string    `json:"fullname"`
		Age      int       `json:"age"`
		Children []*Person `json:"children,omitempty"`
	}

	got := &Person{
		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)

	got.Children = []*Person{
		{Fullname: "Alice", Age: 28},
		{Fullname: "Brian", Age: 22},
	}
	ok = td.Cmp(t, got,
		td.JSON(`{"age": $age, "fullname": $name, "children": $children}`,
			td.Tag("age", td.Between(40, 45)),
			td.Tag("name", td.HasSuffix("Foobar")),
			td.Tag("children", td.Bag(
				&Person{Fullname: "Brian", Age: 22},
				&Person{Fullname: "Alice", Age: 28},
			))))
	fmt.Println("check got w/named placeholders, and children w/go structs:", ok)

	ok = td.Cmp(t, got,
		td.JSON(`{"age": Between($1, $2), "fullname": HasSuffix($suffix), "children": Len(2)}`,
			40, 45,
			td.Tag("suffix", "Foobar")))
	fmt.Println("check got w/num & named placeholders:", ok)

}
Output:

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 w/named placeholders, and children w/go structs: true
check got w/num & named placeholders: true
Example (RawStrings)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type details struct {
		Address string `json:"address"`
		Car     string `json:"car"`
	}

	got := &struct {
		Fullname string  `json:"fullname"`
		Age      int     `json:"age"`
		Details  details `json:"details"`
	}{
		Fullname: "Foo Bar",
		Age:      42,
		Details: details{
			Address: "something",
			Car:     "Peugeot",
		},
	}

	ok := td.Cmp(t, got,
		td.JSON(`
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty, // () are optional when no parameters
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`))
	fmt.Println("Original:", ok)

	ok = td.Cmp(t, got,
		td.JSON(`
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({\n\"address\": NotEmpty,\n\"car\": Any(\"Peugeot\", \"Tesla\", \"Jeep\")\n})"
}`))
	fmt.Println("JSON compliant:", ok)

	ok = td.Cmp(t, got,
		td.JSON(`
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    \"address\": NotEmpty, // () are optional when no parameters
    \"car\":     Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these
  })"
}`))
	fmt.Println("JSON multilines strings:", ok)

	ok = td.Cmp(t, got,
		td.JSON(`
{
  "fullname": "$^HasPrefix(r<Foo>)",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    r<address>: NotEmpty, // () are optional when no parameters
    r<car>:     Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these
  })"
}`))
	fmt.Println("Raw strings:", ok)

}
Output:

Original: true
JSON compliant: true
JSON multilines strings: true
Raw strings: true

func JSONPointer added in v1.8.0

func JSONPointer(ptr string, expectedValue any) TestDeep

JSONPointer is a smuggler operator. It takes the JSON representation of data, gets the value corresponding to the JSON pointer ptr (as RFC 6901 specifies it) and compares it to expectedValue.

Lax mode is automatically enabled to simplify numeric tests.

JSONPointer does its best to convert back the JSON pointed data to the type of expectedValue or to the type behind the expectedValue operator, if it is an operator. Allowing to do things like:

type Item struct {
  Val  int   `json:"val"`
  Next *Item `json:"next"`
}
got := Item{Val: 1, Next: &Item{Val: 2, Next: &Item{Val: 3}}}

td.Cmp(t, got, td.JSONPointer("/next/next", Item{Val: 3}))
td.Cmp(t, got, td.JSONPointer("/next/next", &Item{Val: 3}))
td.Cmp(t,
  got,
  td.JSONPointer("/next/next",
    td.Struct(Item{}, td.StructFields{"Val": td.Gte(3)})),
)

got := map[string]int64{"zzz": 42} // 42 is int64 here
td.Cmp(t, got, td.JSONPointer("/zzz", 42))
td.Cmp(t, got, td.JSONPointer("/zzz", td.Between(40, 45)))

Of course, it does this conversion only if the expected type can be guessed. In the case the conversion cannot occur, data is compared as is, in its freshly unmarshaled JSON form (so as bool, float64, string, []any, map[string]any or simply nil).

Note that as any TestDeep operator can be used as expectedValue, JSON operator works out of the box:

got := json.RawMessage(`{"foo":{"bar": {"zip": true}}}`)
td.Cmp(t, got, td.JSONPointer("/foo/bar", td.JSON(`{"zip": true}`)))

It can be used with structs lacking json tags. In this case, fields names have to be used in JSON pointer:

type Item struct {
  Val  int
  Next *Item
}
got := Item{Val: 1, Next: &Item{Val: 2, Next: &Item{Val: 3}}}

td.Cmp(t, got, td.JSONPointer("/Next/Next", Item{Val: 3}))

Contrary to Smuggle operator and its fields-path feature, only public fields can be followed, as private ones are never (un)marshaled.

There is no JSONHas nor JSONHasnt operators to only check a JSON pointer exists or not, but they can easily be emulated:

JSONHas := func(pointer string) td.TestDeep {
  return td.JSONPointer(pointer, td.Ignore())
}

JSONHasnt := func(pointer string) td.TestDeep {
  return td.Not(td.JSONPointer(pointer, td.Ignore()))
}

TypeBehind method always returns nil as the expected type cannot be guessed from a JSON pointer.

See also JSON, SubJSONOf, SuperJSONOf, Smuggle and Flatten.

Example (Has_hasnt)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := json.RawMessage(`
{
  "name": "Bob",
  "age": 42,
  "children": [
    {
      "name": "Alice",
      "age": 16
    },
    {
      "name": "Britt",
      "age": 21,
      "children": [
        {
          "name": "John",
          "age": 1
        }
      ]
    }
  ]
}`)

	// Has Bob some children?
	ok := td.Cmp(t, got, td.JSONPointer("/children", td.Len(td.Gt(0))))
	fmt.Println("Bob has at least one child:", ok)

	// But checking "children" exists is enough here
	ok = td.Cmp(t, got, td.JSONPointer("/children/0/children", td.Ignore()))
	fmt.Println("Alice has children:", ok)

	ok = td.Cmp(t, got, td.JSONPointer("/children/1/children", td.Ignore()))
	fmt.Println("Britt has children:", ok)

	// The reverse can be checked too
	ok = td.Cmp(t, got, td.Not(td.JSONPointer("/children/0/children", td.Ignore())))
	fmt.Println("Alice hasn't children:", ok)

	ok = td.Cmp(t, got, td.Not(td.JSONPointer("/children/1/children", td.Ignore())))
	fmt.Println("Britt hasn't children:", ok)

}
Output:

Bob has at least one child: true
Alice has children: false
Britt has children: true
Alice hasn't children: true
Britt hasn't children: false
Example (Rfc6901)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := json.RawMessage(`
{
   "foo":  ["bar", "baz"],
   "":     0,
   "a/b":  1,
   "c%d":  2,
   "e^f":  3,
   "g|h":  4,
   "i\\j": 5,
   "k\"l": 6,
   " ":    7,
   "m~n":  8
}`)

	expected := map[string]any{
		"foo": []any{"bar", "baz"},
		"":    0,
		"a/b": 1,
		"c%d": 2,
		"e^f": 3,
		"g|h": 4,
		`i\j`: 5,
		`k"l`: 6,
		" ":   7,
		"m~n": 8,
	}
	ok := td.Cmp(t, got, td.JSONPointer("", expected))
	fmt.Println("Empty JSON pointer means all:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/foo`, []any{"bar", "baz"}))
	fmt.Println("Extract `foo` key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/foo/0`, "bar"))
	fmt.Println("First item of `foo` key slice:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/`, 0))
	fmt.Println("Empty key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/a~1b`, 1))
	fmt.Println("Slash has to be escaped using `~1`:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/c%d`, 2))
	fmt.Println("% in key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/e^f`, 3))
	fmt.Println("^ in key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/g|h`, 4))
	fmt.Println("| in key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/i\j`, 5))
	fmt.Println("Backslash in key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/k"l`, 6))
	fmt.Println("Double-quote in key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/ `, 7))
	fmt.Println("Space key:", ok)

	ok = td.Cmp(t, got, td.JSONPointer(`/m~0n`, 8))
	fmt.Println("Tilde has to be escaped using `~0`:", ok)

}
Output:

Empty JSON pointer means all: true
Extract `foo` key: true
First item of `foo` key slice: true
Empty key: true
Slash has to be escaped using `~1`: true
% in key: true
^ in key: true
| in key: true
Backslash in key: true
Double-quote in key: true
Space key: true
Tilde has to be escaped using `~0`: true
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// Without json tags, encoding/json uses public fields name
	type Item struct {
		Name  string
		Value int64
		Next  *Item
	}

	got := Item{
		Name:  "first",
		Value: 1,
		Next: &Item{
			Name:  "second",
			Value: 2,
			Next: &Item{
				Name:  "third",
				Value: 3,
			},
		},
	}

	ok := td.Cmp(t, got, td.JSONPointer("/Next/Next/Name", "third"))
	fmt.Println("3rd item name is `third`:", ok)

	ok = td.Cmp(t, got, td.JSONPointer("/Next/Next/Value", td.Gte(int64(3))))
	fmt.Println("3rd item value is greater or equal than 3:", ok)

	ok = td.Cmp(t, got,
		td.JSONPointer("/Next",
			td.JSONPointer("/Next",
				td.JSONPointer("/Value", td.Gte(int64(3))))))
	fmt.Println("3rd item value is still greater or equal than 3:", ok)

	ok = td.Cmp(t, got, td.JSONPointer("/Next/Next/Next/Name", td.Ignore()))
	fmt.Println("4th item exists and has a name:", ok)

	// Struct comparison work with or without pointer: &Item{…} works too
	ok = td.Cmp(t, got, td.JSONPointer("/Next/Next", Item{
		Name:  "third",
		Value: 3,
	}))
	fmt.Println("3rd item full comparison:", ok)

}
Output:

3rd item name is `third`: true
3rd item value is greater or equal than 3: true
3rd item value is still greater or equal than 3: true
4th item exists and has a name: false
3rd item full comparison: true

func Keys

func Keys(val any) 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

See also Values and ContainsKey.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// Keys tests keys in an ordered manner
	ok := td.Cmp(t, got, td.Keys([]string{"bar", "foo", "zip"}))
	fmt.Println("All sorted keys are found:", ok)

	// If the expected keys are not ordered, it fails
	ok = td.Cmp(t, got, td.Keys([]string{"zip", "bar", "foo"}))
	fmt.Println("All unsorted keys are found:", ok)

	// To circumvent that, one can use Bag operator
	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)

	// Check that each key is 3 bytes long
	ok = td.Cmp(t, got, td.Keys(td.ArrayEach(td.Len(3))))
	fmt.Println("Each key is 3 bytes long:", ok)

}
Output:

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 Last added in v1.13.0

func Last(filter, expectedValue any) TestDeep

Last is a smuggler operator. It takes an array, a slice or a pointer on array/slice. For each item it applies filter, a TestDeep operator or a function returning a bool. It takes the last item for which the filter matched and compares it to expectedValue. The filter matches when it is a:

  • TestDeep operator and it matches for the item;
  • function receiving the item and it returns true.

expectedValue can of course be a TestDeep operator.

got := []int{-3, -2, -1, 0, 1, 2, 3}
td.Cmp(t, got, td.Last(td.Lt(0), -1))                                   // succeeds
td.Cmp(t, got, td.Last(func(x int) bool { return x%2 == 0 }, 2))        // succeeds
td.Cmp(t, got, td.Last(func(x int) bool { return x%2 == 0 }, td.Gt(0))) // succeeds

If the input is empty (and/or nil for a slice), an "item not found" error is raised before comparing to expectedValue.

var got []int
td.Cmp(t, got, td.Last(td.Gt(0), td.Gt(0)))      // fails
td.Cmp(t, []int{}, td.Last(td.Gt(0), td.Gt(0)))  // fails
td.Cmp(t, [0]int{}, td.Last(td.Gt(0), td.Gt(0))) // fails

See also First and Grep.

Example (Classic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := []int{-3, -2, -1, 0, 1, 2, 3}

	ok := td.Cmp(t, got, td.Last(td.Lt(0), -1))
	fmt.Println("last negative number is -1:", ok)

	isEven := func(x int) bool { return x%2 == 0 }

	ok = td.Cmp(t, got, td.Last(isEven, 2))
	fmt.Println("last even number is 2:", ok)

	ok = td.Cmp(t, got, td.Last(isEven, td.Gt(0)))
	fmt.Println("last even number is > 0:", ok)

	ok = td.Cmp(t, got, td.Last(isEven, td.Code(isEven)))
	fmt.Println("last even number is well even:", ok)

}
Output:

last negative number is -1: true
last even number is 2: true
last even number is > 0: true
last even number is well even: true
Example (Empty)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.Cmp(t, ([]int)(nil), td.Last(td.Gt(0), td.Gt(0)))
	fmt.Println("last in nil slice:", ok)

	ok = td.Cmp(t, []int{}, td.Last(td.Gt(0), td.Gt(0)))
	fmt.Println("last in empty slice:", ok)

	ok = td.Cmp(t, &[]int{}, td.Last(td.Gt(0), td.Gt(0)))
	fmt.Println("last in empty pointed slice:", ok)

	ok = td.Cmp(t, [0]int{}, td.Last(td.Gt(0), td.Gt(0)))
	fmt.Println("last in empty array:", ok)

}
Output:

last in nil slice: false
last in empty slice: false
last in empty pointed slice: false
last in empty array: false
Example (Json)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := map[string]any{
		"values": []int{1, 2, 3, 4},
	}
	ok := td.Cmp(t, got, td.JSON(`{"values": Last(Lt(3), 2)}`))
	fmt.Println("last number < 3:", ok)

	got = map[string]any{
		"persons": []map[string]any{
			{"id": 1, "name": "Joe"},
			{"id": 2, "name": "Bob"},
			{"id": 3, "name": "Alice"},
			{"id": 4, "name": "Brian"},
			{"id": 5, "name": "Britt"},
		},
	}
	ok = td.Cmp(t, got, td.JSON(`
{
  "persons": Last(JSONPointer("/name", "Brian"), {"id": 4, "name": "Brian"})
}`))
	fmt.Println(`is "Brian" content OK:`, ok)

	ok = td.Cmp(t, got, td.JSON(`
{
  "persons": Last(JSONPointer("/name", "Brian"), JSONPointer("/id", 4))
}`))
	fmt.Println(`ID of "Brian" is 4:`, ok)

}
Output:

last number < 3: true
is "Brian" content OK: true
ID of "Brian" is 4: true
Example (Struct)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Fullname string `json:"fullname"`
		Age      int    `json:"age"`
	}

	got := []*Person{
		{
			Fullname: "Bob Foobar",
			Age:      42,
		},
		{
			Fullname: "Alice Bingo",
			Age:      37,
		},
	}

	ok := td.Cmp(t, got, td.Last(
		td.Smuggle("Age", td.Gt(30)),
		td.Smuggle("Fullname", "Alice Bingo")))
	fmt.Println("last person.Age > 30 → Alice:", ok)

	ok = td.Cmp(t, got, td.Last(
		td.JSONPointer("/age", td.Gt(30)),
		td.SuperJSONOf(`{"fullname":"Alice Bingo"}`)))
	fmt.Println("last person.Age > 30 → Alice, using JSON:", ok)

	ok = td.Cmp(t, got, td.Last(
		td.JSONPointer("/age", td.Gt(30)),
		td.JSONPointer("/fullname", td.HasPrefix("Alice"))))
	fmt.Println("first person.Age > 30 → Alice, using JSONPointer:", ok)

}
Output:

last person.Age > 30 → Alice: true
last person.Age > 30 → Alice, using JSON: true
first person.Age > 30 → Alice, using JSONPointer: true

func Lax

func Lax(expectedValue any) 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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	type myInt uint16
	gotMyInt := myInt(1236)

	expected := td.Between(1230, 1240) // int type here

	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)

}
Output:

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 any) 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)))

See also Cap.

Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true
Example (OperatorMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (OperatorSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
true

func Lt

func Lt(maxExpectedValue any) TestDeep

Lt operator checks that data is lesser than maxExpectedValue. maxExpectedValue can be any numeric, string, time.Time (or assignable) value or implements at least one of the two following methods:

func (a T) Less(b T) bool   // returns true if a < b
func (a T) Compare(b T) int // returns -1 if a < b, 1 if a > b, 0 if a == b

maxExpectedValue must be the same type as the compared value, except if BeLax config flag is true.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false

func Lte

func Lte(maxExpectedValue any) TestDeep

Lte operator checks that data is lesser or equal than maxExpectedValue. maxExpectedValue can be any numeric, string, time.Time (or assignable) value or implements at least one of the two following methods:

func (a T) Less(b T) bool   // returns true if a < b
func (a T) Compare(b T) int // returns -1 if a < b, 1 if a > b, 0 if a == b

maxExpectedValue must be the same type as the compared value, except if BeLax config flag is true.

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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func Map

func Map(model any, 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 operators 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.

See also SubMapOf and SuperMapOf.

Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
true

func MapEach

func MapEach(expectedValue any) TestDeep

MapEach operator has to be applied on maps. It compares each value of data map against expectedValue. 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)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func N

func N(num any, tolerance ...any) 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 type as the compared value, except if BeLax config flag is true.

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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

See also NotNaN.

Example (Float32)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

}
Output:

float32(math.NaN()) is float32 not-a-number: true
float32(12) is float32 not-a-number: false
Example (Float64)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

	// math.NaN() is not-a-number: true
	// float64(12) is not-a-number: false
}
Output:

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

See also Empty, NotNil and Zero.

Example
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	var got fmt.Stringer // interface

	// nil value can be compared directly with nil, no need of Nil() here
	ok := td.Cmp(t, got, nil)
	fmt.Println(ok)

	// But it works with Nil() anyway
	ok = td.Cmp(t, got, td.Nil())
	fmt.Println(ok)

	got = (*bytes.Buffer)(nil)

	// In the case of an interface containing a nil pointer, comparing
	// with nil fails, as the interface is not nil
	ok = td.Cmp(t, got, nil)
	fmt.Println(ok)

	// In this case Nil() succeed
	ok = td.Cmp(t, got, td.Nil())
	fmt.Println(ok)

}
Output:

true
true
false
true

func None

func None(notExpectedValues ...any) 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

See also All, Any and Not.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)
	}

}
Output:

true
false
false
9 → true
3 → false
8 → false
15 → false

func Not

func Not(notExpected any) 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

See also None.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
false

func NotAny

func NotAny(notExpectedItems ...any) 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-[]any 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(…)).

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.

See also Set, SubSetOf and SuperSetOf.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using notExpected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

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
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.Cmp(t, nil, td.NotEmpty()) // fails, as nil is considered empty
	fmt.Println(ok)

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

	// Fails as 0 is a number, so not empty. Use NotZero() instead
	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()) // succeeds, NotEmpty() is not NotZero()!
	fmt.Println(ok)

}
Output:

false
true
false
true
true
true
Example (Pointers)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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()) // Ptr() not needed
	fmt.Println(ok)

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

	// Works the same for array, map, channel and string

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

	ok = td.Cmp(t, &MyStruct{}, td.NotEmpty()) // fails, use NotZero() instead
	fmt.Println(ok)

}
Output:

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

See also NaN.

Example (Float32)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

}
Output:

float32(math.NaN()) is NOT float32 not-a-number: false
float32(12) is NOT float32 not-a-number: true
Example (Float64)
package main

import (
	"fmt"
	"math"
	"testing"

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

func main() {
	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)

	// math.NaN() is NOT not-a-number: false
	// float64(12) is NOT not-a-number: true
}
Output:

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

See also Nil, NotEmpty and NotZero.

Example
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// nil value can be compared directly with Not(nil), no need of NotNil() here
	ok := td.Cmp(t, got, td.Not(nil))
	fmt.Println(ok)

	// But it works with NotNil() anyway
	ok = td.Cmp(t, got, td.NotNil())
	fmt.Println(ok)

	got = (*bytes.Buffer)(nil)

	// In the case of an interface containing a nil pointer, comparing
	// with Not(nil) succeeds, as the interface is not nil
	ok = td.Cmp(t, got, td.Not(nil))
	fmt.Println(ok)

	// In this case NotNil() fails
	ok = td.Cmp(t, got, td.NotNil())
	fmt.Println(ok)

}
Output:

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

See also NotEmpty, NotNil and Zero.

Example
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	ok := td.Cmp(t, 0, td.NotZero()) // fails
	fmt.Println(ok)

	ok = td.Cmp(t, float64(0), td.NotZero()) // fails
	fmt.Println(ok)

	ok = td.Cmp(t, 12, td.NotZero())
	fmt.Println(ok)

	ok = td.Cmp(t, (map[string]int)(nil), td.NotZero()) // fails, as nil
	fmt.Println(ok)

	ok = td.Cmp(t, map[string]int{}, td.NotZero()) // succeeds, as not nil
	fmt.Println(ok)

	ok = td.Cmp(t, ([]int)(nil), td.NotZero()) // fails, as nil
	fmt.Println(ok)

	ok = td.Cmp(t, []int{}, td.NotZero()) // succeeds, as not nil
	fmt.Println(ok)

	ok = td.Cmp(t, [3]int{}, td.NotZero()) // fails
	fmt.Println(ok)

	ok = td.Cmp(t, [3]int{0, 1}, td.NotZero()) // succeeds, DATA[1] is not 0
	fmt.Println(ok)

	ok = td.Cmp(t, bytes.Buffer{}, td.NotZero()) // fails
	fmt.Println(ok)

	ok = td.Cmp(t, &bytes.Buffer{}, td.NotZero()) // succeeds, as pointer not nil
	fmt.Println(ok)

	ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.NotZero())) // fails as deref by Ptr()
	fmt.Println(ok)

}
Output:

false
false
true
false
true
false
true
false
true
false
true
false

func PPtr

func PPtr(val any) 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).

See also Ptr.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func Ptr

func Ptr(val any) 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).

See also PPtr and Shallow.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func Re

func Re(reg any, capture ...any) 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.Compile, 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

See also ReAll.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (Capture)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (Compiled)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (CompiledCapture)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
false
Example (CompiledError)
package main

import (
	"errors"
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (CompiledStringer)
package main

import (
	"bytes"
	"fmt"
	"regexp"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foo bar")
	ok := td.Cmp(t, got, td.Re(expected), "checks value %s", got)
	fmt.Println(ok)

}
Output:

true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foo bar")
	ok := td.Cmp(t, got, td.Re("(zip|bar)$"), "checks value %s", got)
	fmt.Println(ok)

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foo bar")
	ok := td.Cmp(t, got, td.Re("(zip|bar)$"), "checks value %s", got)
	fmt.Println(ok)

}
Output:

true

func ReAll

func ReAll(reg, capture any) 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.Compile, 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

See also Re.

Example (Capture)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// Matches, but all catured groups do not match Set
	got = "foo BAR biz"
	ok = td.Cmp(t, got, td.ReAll(`(\w+)`, td.Set("biz", "foo", "bar")),
		"checks value %s", got)
	fmt.Println(ok)

}
Output:

true
false
Example (CaptureComplex)
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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)

	// Matches, but 11 is not greater than 20
	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)

}
Output:

true
false
Example (CompiledCapture)
package main

import (
	"fmt"
	"regexp"
	"testing"

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

func main() {
	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)

	// Matches, but all catured groups do not match Set
	got = "foo BAR biz"
	ok = td.Cmp(t, got, td.ReAll(expected, td.Set("biz", "foo", "bar")),
		"checks value %s", got)
	fmt.Println(ok)

}
Output:

true
false
Example (CompiledCaptureComplex)
package main

import (
	"fmt"
	"regexp"
	"strconv"
	"testing"

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

func main() {
	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)

	// Matches, but 11 is not greater than 20
	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)

}
Output:

true
false

func Recv added in v1.13.0

func Recv(expectedValue any, timeout ...time.Duration) TestDeep

Recv is a smuggler operator. It reads from a channel or a pointer to a channel and compares the read value to expectedValue.

expectedValue can be any value including a TestDeep operator. It can also be RecvNothing to test nothing can be read from the channel or RecvClosed to check the channel is closed.

If timeout is passed it should be only one item. It means: try to read the channel during this duration to get a value before giving up. If timeout is missing or ≤ 0, it defaults to 0 meaning Recv does not wait for a value but gives up instantly if no value is available on the channel.

ch := make(chan int, 6)
td.Cmp(t, ch, td.Recv(td.RecvNothing)) // succeeds
td.Cmp(t, ch, td.Recv(42))             // fails, nothing to receive
// recv(DATA): values differ
//      got: nothing received on channel
// expected: 42

ch <- 42
td.Cmp(t, ch, td.Recv(td.RecvNothing)) // fails, 42 received instead
// recv(DATA): values differ
//      got: 42
// expected: nothing received on channel

td.Cmp(t, ch, td.Recv(42)) // fails, nothing to receive anymore
// recv(DATA): values differ
//      got: nothing received on channel
// expected: 42

ch <- 666
td.Cmp(t, ch, td.Recv(td.Between(600, 700))) // succeeds

close(ch)
td.Cmp(t, ch, td.Recv(td.RecvNothing)) // fails as channel is closed
// recv(DATA): values differ
//      got: channel is closed
// expected: nothing received on channel

td.Cmp(t, ch, td.Recv(td.RecvClosed)) // succeeds

Note that for convenience Recv accepts pointer on channel:

ch := make(chan int, 6)
ch <- 42
td.Cmp(t, &ch, td.Recv(42)) // succeeds

Each time Recv is called, it tries to consume one item from the channel, immediately or, if given, before timeout duration. To consume several items in a same Cmp call, one can use All operator as in:

ch := make(chan int, 6)
ch <- 1
ch <- 2
ch <- 3
close(ch)
td.Cmp(t, ch, td.All( // succeeds
  td.Recv(1),
  td.Recv(2),
  td.Recv(3),
  td.Recv(td.RecvClosed),
))

To check nothing can be received during 100ms on channel ch (if something is received before, including a close, it fails):

td.Cmp(t, ch, td.Recv(td.RecvNothing, 100*time.Millisecond))

note that in case of success, the above Cmp call always lasts 100ms.

To check 42 can be received from channel ch during the next 100ms (if nothing is received during these 100ms or something different from 42, including a close, it fails):

td.Cmp(t, ch, td.Recv(42, 100*time.Millisecond))

note that in case of success, the above Cmp call lasts less than 100ms.

A nil channel is not handled specifically, so it “is never ready for communication” as specification says:

var ch chan int
td.Cmp(t, ch, td.Recv(td.RecvNothing)) // always succeeds
td.Cmp(t, ch, td.Recv(42))             // or any other value, always fails
td.Cmp(t, ch, td.Recv(td.RecvClosed))  // always fails

so to check if a channel is not nil before reading from it, one can either do:

td.Cmp(t, ch, td.All(
  td.NotNil(),
  td.Recv(42),
))
// or
if td.Cmp(t, ch, td.NotNil()) {
  td.Cmp(t, ch, td.Recv(42))
}

TypeBehind method returns the reflect.Type of expectedValue, except if expectedValue is a TestDeep operator. In this case, it delegates TypeBehind() to the operator.

See also Cap and Len.

Example (Basic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := make(chan int, 3)

	ok := td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("nothing to receive:", ok)

	got <- 1
	got <- 2
	got <- 3
	close(got)

	ok = td.Cmp(t, got, td.Recv(1))
	fmt.Println("1st receive is 1:", ok)

	ok = td.Cmp(t, got, td.All(
		td.Recv(2),
		td.Recv(td.Between(3, 4)),
		td.Recv(td.RecvClosed),
	))
	fmt.Println("next receives are 2, 3 then closed:", ok)

	ok = td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("nothing to receive:", ok)

}
Output:

nothing to receive: true
1st receive is 1: true
next receives are 2, 3 then closed: true
nothing to receive: false
Example (ChannelPointer)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := make(chan int, 3)

	ok := td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("nothing to receive:", ok)

	got <- 1
	got <- 2
	got <- 3
	close(got)

	ok = td.Cmp(t, &got, td.Recv(1))
	fmt.Println("1st receive is 1:", ok)

	ok = td.Cmp(t, &got, td.All(
		td.Recv(2),
		td.Recv(td.Between(3, 4)),
		td.Recv(td.RecvClosed),
	))
	fmt.Println("next receives are 2, 3 then closed:", ok)

	ok = td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("nothing to receive:", ok)

}
Output:

nothing to receive: true
1st receive is 1: true
next receives are 2, 3 then closed: true
nothing to receive: false
Example (NilChannel)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	var ch chan int

	ok := td.Cmp(t, ch, td.Recv(td.RecvNothing))
	fmt.Println("nothing to receive from nil channel:", ok)

	ok = td.Cmp(t, ch, td.Recv(42))
	fmt.Println("something to receive from nil channel:", ok)

	ok = td.Cmp(t, ch, td.Recv(td.RecvClosed))
	fmt.Println("is a nil channel closed:", ok)

}
Output:

nothing to receive from nil channel: true
something to receive from nil channel: false
is a nil channel closed: false
Example (WithTimeout)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	got := make(chan int, 1)
	tick := make(chan struct{})

	go func() {
		// ①
		<-tick
		time.Sleep(100 * time.Millisecond)
		got <- 0

		// ②
		<-tick
		time.Sleep(100 * time.Millisecond)
		got <- 1

		// ③
		<-tick
		time.Sleep(100 * time.Millisecond)
		close(got)
	}()

	td.Cmp(t, got, td.Recv(td.RecvNothing))

	// ①
	tick <- struct{}{}
	ok := td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("① RecvNothing:", ok)
	ok = td.Cmp(t, got, td.Recv(0, 150*time.Millisecond))
	fmt.Println("① receive 0 w/150ms timeout:", ok)
	ok = td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("① RecvNothing:", ok)

	// ②
	tick <- struct{}{}
	ok = td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("② RecvNothing:", ok)
	ok = td.Cmp(t, got, td.Recv(1, 150*time.Millisecond))
	fmt.Println("② receive 1 w/150ms timeout:", ok)
	ok = td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("② RecvNothing:", ok)

	// ③
	tick <- struct{}{}
	ok = td.Cmp(t, got, td.Recv(td.RecvNothing))
	fmt.Println("③ RecvNothing:", ok)
	ok = td.Cmp(t, got, td.Recv(td.RecvClosed, 150*time.Millisecond))
	fmt.Println("③ check closed w/150ms timeout:", ok)

}
Output:

① RecvNothing: true
① receive 0 w/150ms timeout: true
① RecvNothing: true
② RecvNothing: true
② receive 1 w/150ms timeout: true
② RecvNothing: true
③ RecvNothing: true
③ check closed w/150ms timeout: true

func SStruct

func SStruct(model any, 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. If the expected type is private or anonymous, model can be nil. In this case it is considered lazy and determined each time the operator is involved in a match, see below.

expectedFields can be omitted, if no TestDeep operators are involved. If expectedFields contains more than one item, all items are merged before their use, from left to right.

To ignore a field, one has to specify it in expectedFields and use the Ignore operator.

td.Cmp(t, got, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "Children": 4,
  },
  td.StructFields{
    "Age":      td.Between(40, 45),
    "Children": td.Ignore(), // overwrite 4
  }),
)

It is an error to set a non-zero field in model AND to set the same field in expectedFields, as in such cases the SStruct operator does not know if the user wants to override the non-zero model field value or if it is an error. To explicitly override a non-zero model in expectedFields, just prefix its name with a ">" (followed by some optional spaces), as in:

td.Cmp(t, got, td.SStruct(
  Person{
    Name:     "John Doe",
    Age:      23,
    Children: 4,
  },
  td.StructFields{
    "> Age":     td.Between(40, 45),
    ">Children": 0, // spaces after ">" are optional
  }),
)

expectedFields can also contain regexps or shell patterns to match multiple fields not explicitly listed in model and in expectedFields. Regexps are prefixed by "=~" or "!~" to respectively match or don't-match. Shell patterns are prefixed by "=" or "!" to respectively match or don't-match.

td.Cmp(t, got, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "=*At":     td.Lte(time.Now()), // matches CreatedAt & UpdatedAt fields using shell pattern
    "=~^[a-z]": td.Ignore(),        // explicitly ignore private fields using a regexp
  }),
)

When several patterns can match a same field, it is advised to tell go-testdeep in which order patterns should be tested, as once a pattern matches a field, the other patterns are ignored for this field. To do so, each pattern can be prefixed by a number, as in:

td.Cmp(t, got, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "1=*At":     td.Lte(time.Now()),
    "2=~^[a-z]": td.NotNil(),
  }),
)

This way, "*At" shell pattern is always used before "^[a-z]" regexp, so if a field "createdAt" exists it is tested against time.Now() and never against NotNil. A pattern without a prefix number is the same as specifying "0" as prefix.

To make it clearer, some spaces can be added, as well as bigger numbers used:

td.Cmp(t, got, td.SStruct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    " 900 =  *At":    td.Lte(time.Now()),
    "2000 =~ ^[a-z]": td.NotNil(),
  }),
)

The following example combines all possibilities:

td.Cmp(t, got, td.SStruct(
  Person{
    NickName: "Joe",
  },
  td.StructFields{
    "Firstname":               td.Any("John", "Johnny"),
    "1 =  *[nN]ame":           td.NotEmpty(), // matches LastName, lastname, …
    "2 !  [A-Z]*":             td.NotZero(),  // matches all private fields
    "3 =~ ^(Crea|Upda)tedAt$": td.Gte(time.Now()),
    "4 !~ ^(Dogs|Children)$":  td.Zero(),   // matches all remaining fields except Dogs and Children
    "5 =~ .":                  td.NotNil(), // matches all remaining fields (same as "5 = *")
  }),
)

If the expected type is private to the current package, it cannot be passed as model. To overcome this limitation, model can be nil, it is then considered as lazy. This way, the model is automatically set during each match to the same type (still requiring struct or struct pointer) of the compared data. Similarly, testing an anonymous struct can be boring as all fields have to be re-declared to define model. A nil model avoids that:

got := struct {
  name string
  age  int
}{"Bob", 42}
td.Cmp(t, got, td.SStruct(nil, td.StructFields{
  "name": "Bob",
  "age":  td.Between(40, 42),
}))

During a match, all expected and zero fields must be found to succeed.

TypeBehind method returns the reflect.Type of model.

See also SStruct.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// NumChildren is not listed in expected fields so it must be zero
	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("Foobar is between 40 & 50:", ok)

	// Model can be empty
	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("Foobar has some children:", ok)

	// Works with pointers too
	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("Foobar has some children (using pointer):", ok)

	// Model does not need to be instanciated
	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("Foobar has some children (using nil model):", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar has some children: true
Foobar has some children (using pointer): true
Foobar has some children (using nil model): true
Example (Lazy_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := struct {
		name string
		age  int
	}{
		name: "Foobar",
		age:  42,
	}

	ok := td.Cmp(t, got, td.SStruct(nil, td.StructFields{
		"name": "Foobar",
		"age":  td.Between(40, 45),
	}))
	fmt.Println("Lazy model:", ok)

	ok = td.Cmp(t, got, td.SStruct(nil, td.StructFields{
		"name": "Foobar",
		"zip":  666,
	}))
	fmt.Println("Lazy model with unknown field:", ok)

}
Output:

Lazy model: true
Lazy model with unknown field: false
Example (Overwrite_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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.SStruct(
			Person{
				Name: "Foobar",
				Age:  53,
			},
			td.StructFields{
				">Age":        td.Between(40, 50), // ">" to overwrite Age:53 in model
				"NumChildren": td.Gt(2),
			}),
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	ok = td.Cmp(t, got,
		td.SStruct(
			Person{
				Name: "Foobar",
				Age:  53,
			},
			td.StructFields{
				"> Age":       td.Between(40, 50), // same, ">" can be followed by spaces
				"NumChildren": td.Gt(2),
			}),
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar is between 40 & 50: true
Example (Patterns)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Firstname string
		Lastname  string
		Surname   string
		Nickname  string
		CreatedAt time.Time
		UpdatedAt time.Time
		DeletedAt *time.Time
		id        int64
		secret    string
	}

	now := time.Now()
	got := Person{
		Firstname: "Maxime",
		Lastname:  "Foo",
		Surname:   "Max",
		Nickname:  "max",
		CreatedAt: now,
		UpdatedAt: now,
		DeletedAt: nil, // not deleted yet
		id:        2345,
		secret:    "5ecr3T",
	}

	ok := td.Cmp(t, got,
		td.SStruct(Person{Lastname: "Foo"}, td.StructFields{
			`DeletedAt`: nil,
			`=  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
			`=~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
			`!  [A-Z]*`: td.Ignore(),        // private fields
		}),
		"mix shell & regexp patterns")
	fmt.Println("Patterns match only remaining fields:", ok)

	ok = td.Cmp(t, got,
		td.SStruct(Person{Lastname: "Foo"}, td.StructFields{
			`DeletedAt`:   nil,
			`1 =  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
			`2 =~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
			`3 !~ ^[A-Z]`: td.Ignore(),        // private fields
		}),
		"ordered patterns")
	fmt.Println("Ordered patterns match only remaining fields:", ok)

}
Output:

Patterns match only remaining fields: true
Ordered patterns match only remaining fields: true
Example (Struct_fields)
package main

import (
	"fmt"
	"testing"

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

func main() { // only operator
	t := &testing.T{}

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// No added value here, but it works
	ok := td.Cmp(t, got,
		td.SStruct(Person{
			Name:        "Foobar",
			Age:         42,
			NumChildren: 3,
		}),
		"no StructFields")
	fmt.Println("Without any StructFields:", ok)

	ok = td.Cmp(t, got,
		td.SStruct(Person{Name: "Bingo"},
			td.StructFields{
				"> Name": "pipo",
				"Age":    42,
			},
			td.StructFields{
				"> Name":      "bingo",
				"NumChildren": 10,
			},
			td.StructFields{
				">Name":       "Foobar",
				"NumChildren": 3,
			}),
		"merge several StructFields")
	fmt.Println("Merge several StructFields:", ok)

}
Output:

Without any StructFields: true
Merge several StructFields: true

func Set

func Set(expectedItems ...any) 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-[]any 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))

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.

See also NotAny, SubSetOf, SuperSetOf and Bag.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// Matches as all items are present, ignoring duplicates
	ok := td.Cmp(t, got, td.Set(1, 2, 3, 5, 8),
		"checks all items are present, in any order")
	fmt.Println(ok)

	// Duplicates are ignored in a Set
	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)

	// Tries its best to not raise an error when a value can be matched
	// by several Set entries
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

true
true
true
true

func Shallow

func Shallow(expectedPtr any) 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

See also Ptr.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// Same contents, but not same pointer
	ok = td.Cmp(t, got, td.Shallow(&MyStruct{Value: 12}),
		"checks pointers only, not contents")
	fmt.Println(ok)

}
Output:

true
false
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

are ≠ but share the same area: true
are = but do not point to same area: false
Example (String)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

are ≠ but share the same area: true
are = but do not point to same area: false

func Slice

func Slice(model any, expectedEntries ArrayEntries) TestDeep

Slice operator compares the contents of a slice or a pointer on a slice against the values of model and the values of expectedEntries. Entries with zero values of model are ignored if the same entry is present in expectedEntries, otherwise they are taken into account. An entry cannot be present in both model and expectedEntries, except if it is a zero-value in model. At the end, all entries are checked. To check only some entries of a slice, see SuperSliceOf operator.

model must be the same type as compared data.

expectedEntries can be nil, if no zero entries are expected and no TestDeep operators 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.

See also Array and SuperSliceOf.

Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true
true
true

func Smuggle

func Smuggle(fn, expectedValue any) 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 convenient shortcuts, fn can be a string specifying a fields-path through structs, maps & slices, or any other type, in this case a simple cast is done (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{ As map[string]*A }
type C struct{ B B }
got := C{B: B{As: map[string]*A{"foo": {Num: 12}}}}

// Tests that got.B.A.Num is 12
td.Cmp(t, got,
  td.Smuggle(func(c C) int {
    return c.B.As["foo"].Num
  }, 12))

As brought up above, a fields-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.As["foo"].Num is 12
td.Cmp(t, got, td.Smuggle("B.As[foo].Num", 12))

Contrary to JSONPointer operator, private fields can be followed. Arrays, slices and maps work using the index/key inside square brackets (e.g. [12] or [foo]). Maps work only for simple key types (string or numbers), without "" when using strings (e.g. [foo]).

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, even on several levels, like in:

type A struct{ N any }
num := 12
pnum := &num
td.Cmp(t, A{N: &pnum}, td.Smuggle("N", 12))

Last but not least, a simple type can be passed as fn to operate a cast, handling specifically strings and slices of bytes:

td.Cmp(t, `{"foo":1}`, td.Smuggle(json.RawMessage{}, td.JSON(`{"foo":1}`)))
// or equally
td.Cmp(t, `{"foo":1}`, td.Smuggle(json.RawMessage(nil), td.JSON(`{"foo":1}`)))

converts on the fly a string to a json.RawMessage so JSON operator can parse it as JSON. This is mostly a shortcut for:

td.Cmp(t, `{"foo":1}`, td.Smuggle(
  func(r json.RawMessage) json.RawMessage { return r },
  td.JSON(`{"foo":1}`)))

except that for strings and slices of bytes (like here), it accepts io.Reader interface too:

var body io.Reader
// …
td.Cmp(t, body, td.Smuggle(json.RawMessage{}, td.JSON(`{"foo":1}`)))
// or equally
td.Cmp(t, body, td.Smuggle(json.RawMessage(nil), td.JSON(`{"foo":1}`)))

This last example allows to easily inject body content into JSON operator.

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 writing of tests (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 and the cast feature are 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 any, as the type can not be known in advance.

See also Code, JSONPointer and Flatten.

Example (Auto_unmarshal)
package main

import (
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// Automatically json.Unmarshal to compare
	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)

}
Output:

JSON contents is OK: true
Example (Cast)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// A string containing JSON
	got := `{ "foo": 123 }`

	// Automatically cast a string to a json.RawMessage so td.JSON can operate
	ok := td.Cmp(t, got,
		td.Smuggle(json.RawMessage{}, td.JSON(`{"foo":123}`)))
	fmt.Println("JSON contents in string is OK:", ok)

	// Automatically read from io.Reader to a json.RawMessage
	ok = td.Cmp(t, bytes.NewReader([]byte(got)),
		td.Smuggle(json.RawMessage{}, td.JSON(`{"foo":123}`)))
	fmt.Println("JSON contents just read is OK:", ok)

}
Output:

JSON contents in string is OK: true
JSON contents just read is OK: true
Example (Complex)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

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

	// Checks that end date is between 17th and 19th February both at 0h
	// for each of these durations in hours

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

		// Simplest way, but in case of Between() failure, error will be bound
		// to DATA<smuggled>, not very clear...
		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)

		// Name the computed value "ComputedEndDate" to render a Between() failure
		// more understandable, so error will be bound to DATA.ComputedEndDate
		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)
	}

}
Output:

false
false
true
true
true
true
Example (Convert)
package main

import (
	"fmt"
	"strconv"
	"testing"

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

func main() {
	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) { //nolint: gocritic
				return strconv.Atoi(numStr)
			},
			td.Between(120, 130)),
		"checks that number in %#v is in [120 .. 130]")
	fmt.Println(ok)

	// Short version :)
	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)

}
Output:

true
true
true
true
true
Example (Field_path)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Body struct {
		Name  string
		Value any
	}
	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},
			},
		},
	}

	// Want to check whether Num is between 100 and 200?
	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)

	// Same, but automagically generated...
	ok = td.Cmp(t, got, td.Smuggle("Request.Body.Value.Num", td.Between(100, 200)))
	fmt.Println("check Num using a fields-path:", ok)

	// And as Request is an anonymous field, can be simplified further
	// as it can be omitted
	ok = td.Cmp(t, got, td.Smuggle("Body.Value.Num", td.Between(100, 200)))
	fmt.Println("check Num using an other fields-path:", ok)

	// Note that maps and array/slices are supported
	got.Request.Body.Value = map[string]any{
		"foo": []any{
			3: map[int]string{666: "bar"},
		},
	}
	ok = td.Cmp(t, got, td.Smuggle("Body.Value[foo][3][666]", "bar"))
	fmt.Println("check fields-path including maps/slices:", ok)

}
Output:

check Num by hand: true
check Num using a fields-path: true
check Num using an other fields-path: true
check fields-path including maps/slices: true
Example (Interface)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

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

	// Do not check the struct itself, but its stringified form
	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)

}
Output:

stringified time.Time OK: true
stringified MyTime OK: false
Example (Lax)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// got is an int16 and Smuggle func input is an int64: it is OK
	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)

}
Output:

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

See also Contains, HasPrefix, HasSuffix, Re and ReAll.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

using string: true
using []byte: true
Example (Error)
package main

import (
	"errors"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := errors.New("foobar")

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

}
Output:

true
Example (Stringer)
package main

import (
	"bytes"
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	// bytes.Buffer implements fmt.Stringer
	got := bytes.NewBufferString("foobar")

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

}
Output:

true

func Struct

func Struct(model any, 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. If the expected type is anonymous or private, model can be nil. In this case it is considered lazy and determined each time the operator is involved in a match, see below.

expectedFields can be omitted, if no zero entries are expected and no TestDeep operators are involved. If expectedFields contains more than one item, all items are merged before their use, from left to right.

td.Cmp(t, got, td.Struct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "Children": 4,
  },
  td.StructFields{
    "Age":      td.Between(40, 45),
    "Children": 0, // overwrite 4
  }),
)

It is an error to set a non-zero field in model AND to set the same field in expectedFields, as in such cases the Struct operator does not know if the user wants to override the non-zero model field value or if it is an error. To explicitly override a non-zero model in expectedFields, just prefix its name with a ">" (followed by some optional spaces), as in:

td.Cmp(t, got, td.Struct(
  Person{
    Name:     "John Doe",
    Age:      23,
    Children: 4,
  },
  td.StructFields{
    "> Age":     td.Between(40, 45),
    ">Children": 0, // spaces after ">" are optional
  }),
)

expectedFields can also contain regexps or shell patterns to match multiple fields not explicitly listed in model and in expectedFields. Regexps are prefixed by "=~" or "!~" to respectively match or don't-match. Shell patterns are prefixed by "=" or "!" to respectively match or don't-match.

td.Cmp(t, got, td.Struct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "=*At":     td.Lte(time.Now()), // matches CreatedAt & UpdatedAt fields using shell pattern
    "=~^[a-z]": td.Ignore(),        // explicitly ignore private fields using a regexp
  }),
)

When several patterns can match a same field, it is advised to tell go-testdeep in which order patterns should be tested, as once a pattern matches a field, the other patterns are ignored for this field. To do so, each pattern can be prefixed by a number, as in:

td.Cmp(t, got, td.Struct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    "1=*At":     td.Lte(time.Now()),
    "2=~^[a-z]": td.NotNil(),
  }),
)

This way, "*At" shell pattern is always used before "^[a-z]" regexp, so if a field "createdAt" exists it is tested against time.Now() and never against NotNil. A pattern without a prefix number is the same as specifying "0" as prefix.

To make it clearer, some spaces can be added, as well as bigger numbers used:

td.Cmp(t, got, td.Struct(
  Person{
    Name: "John Doe",
  },
  td.StructFields{
    " 900 =  *At":    td.Lte(time.Now()),
    "2000 =~ ^[a-z]": td.NotNil(),
  }),
)

The following example combines all possibilities:

td.Cmp(t, got, td.Struct(
  Person{
    NickName: "Joe",
  },
  td.StructFields{
    "Firstname":               td.Any("John", "Johnny"),
    "1 =  *[nN]ame":           td.NotEmpty(), // matches LastName, lastname, …
    "2 !  [A-Z]*":             td.NotZero(),  // matches all private fields
    "3 =~ ^(Crea|Upda)tedAt$": td.Gte(time.Now()),
    "4 !~ ^(Dogs|Children)$":  td.Zero(),   // matches all remaining fields except Dogs and Children
    "5 =~ .":                  td.NotNil(), // matches all remaining fields (same as "5 = *")
  }),
)

If the expected type is private to the current package, it cannot be passed as model. To overcome this limitation, model can be nil, it is then considered as lazy. This way, the model is automatically set during each match to the same type (still requiring struct or struct pointer) of the compared data. Similarly, testing an anonymous struct can be boring as all fields have to be re-declared to define model. A nil model avoids that:

got := struct {
  name string
  age  int
}{"Bob", 42}
td.Cmp(t, got, td.Struct(nil, td.StructFields{"age": td.Between(40, 42)}))

During a match, all expected fields must be found to succeed. Non-expected fields (and so zero model fields) are ignored.

TypeBehind method returns the reflect.Type of model.

See also SStruct.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Name        string
		Age         int
		NumChildren int
	}

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

	// As NumChildren is zero in Struct() call, it is not checked
	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("Foobar is between 40 & 50:", ok)

	// Model can be empty
	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("Foobar has some children:", ok)

	// Works with pointers too
	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("Foobar has some children (using pointer):", ok)

	// Model does not need to be instanciated
	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("Foobar has some children (using nil model):", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar has some children: true
Foobar has some children (using pointer): true
Foobar has some children (using nil model): true
Example (Lazy_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	got := struct {
		name string
		age  int
	}{
		name: "Foobar",
		age:  42,
	}

	ok := td.Cmp(t, got, td.Struct(nil, td.StructFields{
		"name": "Foobar",
		"age":  td.Between(40, 45),
	}))
	fmt.Println("Lazy model:", ok)

	ok = td.Cmp(t, got, td.Struct(nil, td.StructFields{
		"name": "Foobar",
		"zip":  666,
	}))
	fmt.Println("Lazy model with unknown field:", ok)

}
Output:

Lazy model: true
Lazy model with unknown field: false
Example (Overwrite_model)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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",
				Age:  53,
			},
			td.StructFields{
				">Age":        td.Between(40, 50), // ">" to overwrite Age:53 in model
				"NumChildren": td.Gt(2),
			}),
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

	ok = td.Cmp(t, got,
		td.Struct(
			Person{
				Name: "Foobar",
				Age:  53,
			},
			td.StructFields{
				"> Age":       td.Between(40, 50), // same, ">" can be followed by spaces
				"NumChildren": td.Gt(2),
			}),
		"checks %v is the right Person")
	fmt.Println("Foobar is between 40 & 50:", ok)

}
Output:

Foobar is between 40 & 50: true
Foobar is between 40 & 50: true
Example (Patterns)
package main

import (
	"fmt"
	"testing"
	"time"

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

func main() {
	t := &testing.T{}

	type Person struct {
		Firstname string
		Lastname  string
		Surname   string
		Nickname  string
		CreatedAt time.Time
		UpdatedAt time.Time
		DeletedAt *time.Time
	}

	now := time.Now()
	got := Person{
		Firstname: "Maxime",
		Lastname:  "Foo",
		Surname:   "Max",
		Nickname:  "max",
		CreatedAt: now,
		UpdatedAt: now,
		DeletedAt: nil, // not deleted yet
	}

	ok := td.Cmp(t, got,
		td.Struct(Person{Lastname: "Foo"}, td.StructFields{
			`DeletedAt`: nil,
			`=  *name`:  td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
			`=~ At\z`:   td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
		}),
		"mix shell & regexp patterns")
	fmt.Println("Patterns match only remaining fields:", ok)

	ok = td.Cmp(t, got,
		td.Struct(Person{Lastname: "Foo"}, td.StructFields{
			`DeletedAt`:  nil,
			`1 =  *name`: td.Re(`^(?i)max`),  // shell pattern, matches all names except Lastname as in model
			`2 =~ At\z`:  td.Lte(time.Now()), // regexp, matches CreatedAt & UpdatedAt
		}),
		"ordered patterns")
	fmt.Println("Ordered patterns match only remaining fields:", ok)

}
Output:

Patterns match only remaining fields: true
Ordered patterns match only remaining fields: true
Example (Struct_fields)
package main

import (
	"fmt"
	"testing"

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

func main() { // only operator
	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"}), "no StructFields")
	fmt.Println("Without any StructFields:", ok)

	ok = td.Cmp(t, got,
		td.Struct(Person{Name: "Bingo"},
			td.StructFields{
				"> Name": "pipo",
				"Age":    42,
			},
			td.StructFields{
				"> Name":      "bingo",
				"NumChildren": 10,
			},
			td.StructFields{
				">Name":       "Foobar",
				"NumChildren": 3,
			}),
		"merge several StructFields")
	fmt.Println("Merge several StructFields:", ok)

}
Output:

Without any StructFields: true
Merge several StructFields: true

func SubBagOf

func SubBagOf(expectedItems ...any) 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-[]any 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))

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.

See also Bag and SuperBagOf.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// got contains one 8 too many
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

true
false
true
true

func SubJSONOf

func SubJSONOf(expectedJSON any, params ...any) TestDeep

SubJSONOf operator allows to compare the JSON representation of data against expectedJSON. Unlike JSON operator, marshaled 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 os.ReadFile before unmarshaling)
  • []byte containing JSON data
  • encoding/json.RawMessage containing JSON data
  • io.Reader stream containing JSON data (is io.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.

SubJSONOf does its best to convert back the JSON corresponding to a placeholder to the type of the placeholder or, if the placeholder is an operator, to the type behind the operator. Allowing to do things like:

td.Cmp(t, gotValue,
  td.SubJSONOf(`{"foo":$1, "bar": 12}`, []int{1, 2, 3, 4}))
td.Cmp(t, gotValue,
  td.SubJSONOf(`{"foo":$1, "bar": 12}`, []any{1, 2, td.Between(2, 4), 4}))
td.Cmp(t, gotValue,
  td.SubJSONOf(`{"foo":$1, "bar": 12}`, td.Between(27, 32)))

Of course, it does this conversion only if the expected type can be guessed. In the case the conversion cannot occur, data is compared as is, in its freshly unmarshaled JSON form (so as bool, float64, string, []any, map[string]any or simply nil).

Note expectedJSON can be a []byte, an encoding/json.RawMessage, a JSON filename or a 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 */.

Other JSON divergences:

  • ',' can precede a '}' or a ']' (as in go);
  • strings can contain non-escaped \n, \r and \t;
  • raw strings are accepted (r{raw}, r!raw!, …), see below;
  • int_lit & float_lit numbers as defined in go spec are accepted;
  • numbers can be prefixed by '+'.

Most operators can be directly embedded in SubJSONOf without requiring any placeholder. If an operators does not take any parameter, the parenthesis can be omitted.

td.Cmp(t, gotValue,
  td.SubJSONOf(`
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty, // () are optional when no parameters
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`))

Placeholders can be used anywhere, even in operators parameters as in:

td.Cmp(t, gotValue,
  td.SubJSONOf(`{"fullname": HasPrefix($1), "bar": 42}`, "Zip"))

A few notes about operators embedding:

It is also possible to embed operators in JSON strings. This way, the JSON specification can be fulfilled. To avoid collision with possible strings, just prefix the first operator name with "$^". The previous example becomes:

td.Cmp(t, gotValue,
  td.SubJSONOf(`
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    \"address\": NotEmpty, // () are optional when no parameters
    \"car\":     Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these
  })"
}`))

As you can see, in this case, strings in strings have to be escaped. Fortunately, newlines are accepted, but unfortunately they are forbidden by JSON specification. To avoid too much escaping, raw strings are accepted. A raw string is a "r" followed by a delimiter, the corresponding delimiter closes the string. The following raw strings are all the same as "foo\\bar(\"zip\")!":

  • r'foo\bar"zip"!'
  • r,foo\bar"zip"!,
  • r%foo\bar"zip"!%
  • r(foo\bar("zip")!)
  • r{foo\bar("zip")!}
  • r[foo\bar("zip")!]
  • r<foo\bar("zip")!>

So non-bracketing delimiters use the same character before and after, but the 4 sorts of ASCII brackets (round, angle, square, curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot be escaped.

With raw strings, the previous example becomes:

td.Cmp(t, gotValue,
  td.SubJSONOf(`
{
  "fullname": "$^HasPrefix(r<Foo>)",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    r<address>: NotEmpty, // () are optional when no parameters
    r<car>:     Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these
  })"
}`))

Note that raw strings are accepted anywhere, not only in original JSON strings.

To be complete, $^ can prefix an operator even outside a string. This is accepted for compatibility purpose as the first operator embedding feature used this way to embed some operators.

So the following calls are all equivalent:

td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $1}`, td.NotZero()))
td.Cmp(t, gotValue, td.SubJSONOf(`{"id": NotZero}`))
td.Cmp(t, gotValue, td.SubJSONOf(`{"id": NotZero()}`))
td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero}`))
td.Cmp(t, gotValue, td.SubJSONOf(`{"id": $^NotZero()}`))
td.Cmp(t, gotValue, td.SubJSONOf(`{"id": "$^NotZero"}`))
td.Cmp(t, gotValue, td.SubJSONOf(`{"id": "$^NotZero()"}`))

As for placeholders, there is no differences between $^NotZero and "$^NotZero".

Tip: when an io.Reader is expected to contain JSON data, it cannot be tested directly, but using the Smuggle operator simply solves the problem:

var body io.Reader
// …
td.Cmp(t, body, td.Smuggle(json.RawMessage{}, td.SubJSONOf(`{"foo":1,"bar":2}`)))
// or equally
td.Cmp(t, body, td.Smuggle(json.RawMessage(nil), td.SubJSONOf(`{"foo":1,"bar":2}`)))

Smuggle reads from body into an encoding/json.RawMessage then this buffer is unmarshaled by SubJSONOf operator before the comparison.

TypeBehind method returns the map[string]any type.

See also JSON, JSONPointer and SuperJSONOf.

Example (Basic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	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)

	// When the file is already open
	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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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 any, 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 operators 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.

See also Map and SuperMapOf.

Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

true
true

func SubSetOf

func SubSetOf(expectedItems ...any) 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-[]any 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))

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.

See also NotAny, Set and SuperSetOf.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	// Matches as all items are expected, ignoring duplicates
	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)

	// Tries its best to not raise an error when a value can be matched
	// by several SubSetOf entries
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

true
true
true

func SuperBagOf

func SuperBagOf(expectedItems ...any) 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-[]any 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))

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.

See also Bag and SubBagOf.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

true
true
true

func SuperJSONOf

func SuperJSONOf(expectedJSON any, params ...any) TestDeep

SuperJSONOf operator allows to compare the JSON representation of data against expectedJSON. Unlike JSON operator, marshaled 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 os.ReadFile before unmarshaling)
  • []byte containing JSON data
  • encoding/json.RawMessage containing JSON data
  • io.Reader stream containing JSON data (is io.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.

SuperJSONOf does its best to convert back the JSON corresponding to a placeholder to the type of the placeholder or, if the placeholder is an operator, to the type behind the operator. Allowing to do things like:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"foo":$1}`, []int{1, 2, 3, 4}))
td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"foo":$1}`, []any{1, 2, td.Between(2, 4), 4}))
td.Cmp(t, gotValue,
  td.SuperJSONOf(`{"foo":$1}`, td.Between(27, 32)))

Of course, it does this conversion only if the expected type can be guessed. In the case the conversion cannot occur, data is compared as is, in its freshly unmarshaled JSON form (so as bool, float64, string, []any, map[string]any or simply nil).

Note expectedJSON can be a []byte, an encoding/json.RawMessage, a JSON filename or a 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 */.

Other JSON divergences:

  • ',' can precede a '}' or a ']' (as in go);
  • strings can contain non-escaped \n, \r and \t;
  • raw strings are accepted (r{raw}, r!raw!, …), see below;
  • int_lit & float_lit numbers as defined in go spec are accepted;
  • numbers can be prefixed by '+'.

Most operators can be directly embedded in SuperJSONOf without requiring any placeholder. If an operators does not take any parameter, the parenthesis can be omitted.

td.Cmp(t, gotValue,
  td.SuperJSONOf(`
{
  "fullname": HasPrefix("Foo"),
  "age":      Between(41, 43),
  "details":  SuperMapOf({
    "address": NotEmpty, // () are optional when no parameters
    "car":     Any("Peugeot", "Tesla", "Jeep") // any of these
  })
}`))

Placeholders can be used anywhere, even in operators parameters as in:

td.Cmp(t, gotValue, td.SuperJSONOf(`{"fullname": HasPrefix($1)}`, "Zip"))

A few notes about operators embedding:

It is also possible to embed operators in JSON strings. This way, the JSON specification can be fulfilled. To avoid collision with possible strings, just prefix the first operator name with "$^". The previous example becomes:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`
{
  "fullname": "$^HasPrefix(\"Foo\")",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    \"address\": NotEmpty, // () are optional when no parameters
    \"car\":     Any(\"Peugeot\", \"Tesla\", \"Jeep\") // any of these
  })"
}`))

As you can see, in this case, strings in strings have to be escaped. Fortunately, newlines are accepted, but unfortunately they are forbidden by JSON specification. To avoid too much escaping, raw strings are accepted. A raw string is a "r" followed by a delimiter, the corresponding delimiter closes the string. The following raw strings are all the same as "foo\\bar(\"zip\")!":

  • r'foo\bar"zip"!'
  • r,foo\bar"zip"!,
  • r%foo\bar"zip"!%
  • r(foo\bar("zip")!)
  • r{foo\bar("zip")!}
  • r[foo\bar("zip")!]
  • r<foo\bar("zip")!>

So non-bracketing delimiters use the same character before and after, but the 4 sorts of ASCII brackets (round, angle, square, curly) all nest: r[x[y]z] equals "x[y]z". The end delimiter cannot be escaped.

With raw strings, the previous example becomes:

td.Cmp(t, gotValue,
  td.SuperJSONOf(`
{
  "fullname": "$^HasPrefix(r<Foo>)",
  "age":      "$^Between(41, 43)",
  "details":  "$^SuperMapOf({
    r<address>: NotEmpty, // () are optional when no parameters
    r<car>:     Any(r<Peugeot>, r<Tesla>, r<Jeep>) // any of these
  })"
}`))

Note that raw strings are accepted anywhere, not only in original JSON strings.

To be complete, $^ can prefix an operator even outside a string. This is accepted for compatibility purpose as the first operator embedding feature used this way to embed some operators.

So the following calls are all equivalent:

td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $1}`, td.NotZero()))
td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": NotZero}`))
td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": NotZero()}`))
td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero}`))
td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": $^NotZero()}`))
td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero"}`))
td.Cmp(t, gotValue, td.SuperJSONOf(`{"id": "$^NotZero()"}`))

As for placeholders, there is no differences between $^NotZero and "$^NotZero".

Tip: when an io.Reader is expected to contain JSON data, it cannot be tested directly, but using the Smuggle operator simply solves the problem:

var body io.Reader
// …
td.Cmp(t, body, td.Smuggle(json.RawMessage{}, td.SuperJSONOf(`{"foo":1}`)))
// or equally
td.Cmp(t, body, td.Smuggle(json.RawMessage(nil), td.SuperJSONOf(`{"foo":1}`)))

Smuggle reads from body into an encoding/json.RawMessage then this buffer is unmarshaled by SuperJSONOf operator before the comparison.

TypeBehind method returns the map[string]any type.

See also JSON, JSONPointer and SubJSONOf.

Example (Basic)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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)
package main

import (
	"fmt"
	"os"
	"testing"

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

func main() {
	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 := os.MkdirTemp("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir) // clean up

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

	// OK let's test with this file
	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)

	// When the file is already open
	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)

}
Output:

Full match from file name: true
Full match from io.Reader: true
Example (Placeholders)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

}
Output:

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 any, 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 operators 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.

See also SuperMapOf and SubMapOf.

Example (Map)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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 least all expected keys/values", got)
	fmt.Println(ok)

}
Output:

true
Example (TypedMap)
package main

import (
	"fmt"
	"testing"

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

func main() {
	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 least 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 least all expected keys/values",
		got)
	fmt.Println(ok)

}
Output:

true
true

func SuperSetOf

func SuperSetOf(expectedItems ...any) 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-[]any 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))

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.

See also NotAny, Set and SubSetOf.

Example
package main

import (
	"fmt"
	"testing"

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

func main() {
	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)

	// When expected is already a non-[]any slice, it cannot be
	// flattened directly using expected... without copying it to a new
	// []any slice, then use td.Flatten!
	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)

}
Output:

true
true
true

func SuperSliceOf added in v1.10.0

func SuperSliceOf(model any, expectedEntries ArrayEntries) TestDeep

SuperSliceOf operator compares the contents of an array, a pointer on an array, a slice or a pointer on a slice against the non-zero values of model (if any) and the values of expectedEntries. So entries with zero value of model are always ignored. If a zero value check is needed, this zero value has to be set in expectedEntries. An entry cannot be present in both model and expectedEntries, except if it is a zero-value in model. At the end, only entries present in expectedEntries and non-zero ones present in model are checked. To check all entries of an array see Array operator. To check all entries of a slice see Slice operator.

model must be the same type as compared data.

expectedEntries can be nil, if no zero entries are expected and no TestDeep operators are involved.

Works with slices:

got := []int{12, 14, 17}
td.Cmp(t, got, td.SuperSliceOf([]int{12}, nil))                                // succeeds
td.Cmp(t, got, td.SuperSliceOf([]int{12}, td.ArrayEntries{2: 17}))             // succeeds
td.Cmp(t, &got, td.SuperSliceOf(&[]int{0, 14}, td.ArrayEntries{2: td.Gt(16)})) // succeeds

and arrays:

got := [5]int{12, 14, 17, 26, 56}
td.Cmp(t, got, td.SuperSliceOf([5]int{12}, nil))                                // succeeds
td.Cmp(t, got, td.SuperSliceOf([5]int{12}, td.ArrayEntries{2: 17}))             // succeeds
td.Cmp(t, &got, td.SuperSliceOf(&[5]int{0, 14}, td.ArrayEntries{2: td.Gt(16)})) // succeeds

See also Array and Slice.

Example (Array)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	ok := td.Cmp(t, got,
		td.SuperSliceOf([4]int{1: 58}, td.ArrayEntries{3: td.Gt(660)}),
		"checks array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.Cmp(t, got,
		td.SuperSliceOf([4]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf(&[4]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf((*[4]int)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of an array pointer: true
Only check items #0 & #3 of an array pointer, using nil model: true
Example (Slice)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

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

	ok := td.Cmp(t, got,
		td.SuperSliceOf([]int{1: 58}, td.ArrayEntries{3: td.Gt(660)}),
		"checks array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.Cmp(t, got,
		td.SuperSliceOf([]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf(&[]int{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf((*[]int)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of a slice pointer: true
Only check items #0 & #3 of a slice pointer, using nil model: true
Example (TypedArray)
package main

import (
	"fmt"
	"testing"

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

func main() {
	t := &testing.T{}

	type MyArray [4]int

	got := MyArray{42, 58, 26, 666}

	ok := td.Cmp(t, got,
		td.SuperSliceOf(MyArray{1: 58}, td.ArrayEntries{3: td.Gt(660)}),
		"checks typed array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.Cmp(t, got,
		td.SuperSliceOf(MyArray{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf(&MyArray{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf((*MyArray)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of an array pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of an array pointer: true
Only check items #0 & #3 of an array pointer, using nil model: true
Example (TypedSlice)
package main

import (
	"fmt"
	"testing"

	"github.com/maxatome/go-testdeep/td"
)

func main() {
	t := &testing.T{}

	type MySlice []int

	got := MySlice{42, 58, 26, 666}

	ok := td.Cmp(t, got,
		td.SuperSliceOf(MySlice{1: 58}, td.ArrayEntries{3: td.Gt(660)}),
		"checks typed array %v", got)
	fmt.Println("Only check items #1 & #3:", ok)

	ok = td.Cmp(t, got,
		td.SuperSliceOf(MySlice{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf(&MySlice{}, td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer:", ok)

	ok = td.Cmp(t, &got,
		td.SuperSliceOf((*MySlice)(nil), td.ArrayEntries{0: 42, 3: td.Between(660, 670)}),
		"checks array %v", got)
	fmt.Println("Only check items #0 & #3 of a slice pointer, using nil model:", ok)

}
Output:

Only check items #1 & #3: true
Only check items #0 & #3: true
Only check items #0 & #3 of a slice pointer: true
Only check items #0 & #3 of a slice pointer, using nil model: true

func Tag

func Tag(tag string, expectedValue any) 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 any, trunc ...time.Duration) TestDeep

TruncTime operator compares time.Time (or assignable) values after truncating them to the optional trunc duration. See time.Time.Truncate for details about the truncation.

If trunc is missing, it defaults to 0.

During comparison, location does not matter as time.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
package main

import (
	"fmt"
	"testing"
	"time"

	"github.com/maxatome/go-testdeep/td"
)

func main() {
	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")

	// Compare dates ignoring nanoseconds and monotonic parts
	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)

	// Compare dates ignoring time and so monotonic parts
	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)

	// Compare dates exactly but ignoring monotonic part
	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)

}
Output:

true
true
true

func Values

func Values(val any) 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

See also Keys.

Example
package main

import (
	"fmt"
	"testing"

	"github.com/maxatome/go-testdeep/td"
)

func main() {
	t := &testing.T{}

	got := map[string]int{"foo": 1, "bar": 2, "zip": 3}

	// Values tests values in an ordered manner
	ok := td.Cmp(t, got, td.Values([]int{1, 2, 3}))
	fmt.Println("All sorted values are found:", ok)

	// If the expected values are not ordered, it fails
	ok = td.Cmp(t, got, td.Values([]int{3, 1, 2}))
	fmt.Println("All unsorted values are found:", ok)

	// To circumvent that, one can use Bag operator
	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)

	// Check that each value is between 1 and 3
	ok = td.Cmp(t, got, td.Values(td.ArrayEach(td.Between(1, 3))))
	fmt.Println("Each value is between 1 and 3:", ok)

}
Output:

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

See also Empty, Nil and NotZero.

Example
package main

import (
	"bytes"
	"fmt"
	"testing"

	"github.com/maxatome/go-testdeep/td"
)

func main() {
	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()) // fails, as 12 is not 0 :)
	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()) // fails, as not nil
	fmt.Println(ok)

	ok = td.Cmp(t, ([]int)(nil), td.Zero())
	fmt.Println(ok)

	ok = td.Cmp(t, []int{}, td.Zero()) // fails, as not nil
	fmt.Println(ok)

	ok = td.Cmp(t, [3]int{}, td.Zero())
	fmt.Println(ok)

	ok = td.Cmp(t, [3]int{0, 1}, td.Zero()) // fails, DATA[1] is not 0
	fmt.Println(ok)

	ok = td.Cmp(t, bytes.Buffer{}, td.Zero())
	fmt.Println(ok)

	ok = td.Cmp(t, &bytes.Buffer{}, td.Zero()) // fails, as pointer not nil
	fmt.Println(ok)

	ok = td.Cmp(t, &bytes.Buffer{}, td.Ptr(td.Zero())) // OK with the help of Ptr()
	fmt.Println(ok)

}
Output:

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 ...any)
	Fatal(args ...any)
	Helper()
}

TestingT is the minimal interface used by Cmp to report errors. It is commonly implemented by *testing.T and *testing.B.

type Tuple added in v1.8.0

type Tuple interface {
	// Len returns t length, aka the number of items the tuple contains.
	Len() int
	// Index returns t's i'th element. It panics if i is out of range.
	Index(int) any
}

A Tuple is an immutable container. It is used to easily compare several values at once, typically when a function returns several values:

price := func(p float64) (float64, string, error) {
  if p < 0 {
    return 0, "", errors.New("negative price not supported")
  }
  return p * 1.2, "€", nil
}

td.Cmp(t,
  td.TupleFrom(price(10)),
  td.TupleFrom(float64(12), "€", nil),
)

td.Cmp(t,
  td.TupleFrom(price(-10)),
  td.TupleFrom(float64(0), "", td.Not(nil)),
)

Once initialized with TupleFrom, a Tuple is immutable.

func TupleFrom added in v1.8.0

func TupleFrom(vals ...any) Tuple

TupleFrom returns a new Tuple initialized to the values of vals.

td.TupleFrom(float64(0), "", td.Not(nil))

Flatten can be used to flatten non-[]any slice/array into a new Tuple:

ints := []int64{1, 2, 3}
td.TupleFrom(td.Flatten(ints), "OK", nil)

is the same as:

td.TupleFrom(int64(1), int64(2), int64(3), "OK", nil)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL