A balanced test framework for Go.
Only the last two minor versions of Go are officially supported.
$ go get github.com/JosiahWitt/ensure
# Requires Go 1.21+
$ go install github.com/JosiahWitt/ensure/cmd/ensure@latest
Ensure supports Go 1.13+ error comparisons (using errors.Is
), and provides easy to read diffs (using deep.Equal
).
Ensure also supports mocks using GoMock.
Ensure was partially inspired by the is
testing mini-framework.
Creating a test instance starts by calling:
ensure := ensure.New(t)
Then, ensure
can be used as a function to asset a value is correct, using the pattern ensure(<actual>).<Method>(<expected>)
. Methods can also be called on ensure
, using the pattern ensure.<Method>()
.
The ensure
CLI is configured using a .ensure.yml
file which is located in the root of your Go Module (next to the go.mod
file).
Source code for the ensure
CLI is located in the cmd/ensure
directory.
Here is an example .ensure.yml
file:
mocks:
# Used as the directory path relative to the root of the module
# for any interfaces that are not within internal directories.
# Optional, defaults to "internal/mocks".
primaryDestination: internal/mocks
# Used as the directory path relative to internal directories within the project.
# Optional, defaults to "mocks".
internalDestination: mocks
# Tidy mocks after generation completes.
# Automatically runs 'ensure mocks tidy' after 'ensure mocks generate' completes.
# Tidy removes any files that would not be generated by the provided packages list.
# Optional, defaults to true.
tidyAfterGenerate: true
# Packages with interfaces for which to generate mocks
packages:
- path: github.com/my/app/some/pkg
interfaces: [Iface1, Iface2]
func TestBasicExample(t *testing.T) {
ensure := ensure.New(t)
...
// Methods can be called on ensure, for example, Run:
ensure.Run("my subtest", func(ensure ensuring.E) {
...
// To ensure a value is correct, use ensure as a function:
ensure("abc").Equals("abc")
ensure(produceError()).IsError(expectedError)
ensure(doNotProduceError()).IsNotError()
ensure(true).IsTrue()
ensure(false).IsFalse()
ensure("").IsEmpty()
// Failing a test directly:
ensure.Failf("Something went wrong, and we stop the test immediately")
})
}
func TestTableDrivenExample(t *testing.T) {
ensure := ensure.New(t)
table := []struct {
Name string
Input string
IsEmpty bool
}{
{
Name: "with non empty input",
Input: "my string",
IsEmpty: false,
},
{
Name: "with empty input",
Input: "",
IsEmpty: true,
},
}
ensure.RunTableByIndex(table, func(ensure Ensure, i int) {
entry := table[i]
isEmpty := strs.IsEmpty(entry.Input)
ensure(isEmpty).Equals(entry.IsEmpty)
})
}
Mocks can be generated by running ensure mocks generate
, which wraps GoMock.
To install the ensure
CLI, see the Install section.
// db/db.go
type DB interface {
Put(id string, data interface{}) error
...
}
// user/user.go
type UserStorage struct {
DB db.DB
...
}
type User struct {
ID string
Name string
...
}
func (s *UserStorage) Save(ctx context.Context, u *User) error { ... }
// user/user_test.go
func TestTableDrivenMocksExample(t *testing.T) {
ensure := ensure.New(t)
type Mocks struct {
mocksets.DefaultMocks
DB *mock_db.MockDB // Mock of the db.DB interface generated by `ensure mocks generate`
}
table := []struct {
Name string
Input *user.User
ExpectedError error
Mocks *Mocks // Mocks to automatically initialize
SetupMocks func(*Mocks) // Optional function to allow for mock setup
Subject *user.UserStorage // Optional subject containing interfaces with which to assign the mocks
}{
{
Name: "with valid user",
Input: &user.User{
ID: "my-id",
Name: "Mary",
},
SetupMocks: func(m *Mocks) {
m.DB.EXPECT().Put("my-id", &user.User{
ID: "my-id",
Name: "Mary",
})
},
},
{
Name: "with missing ID",
Input: &user.User{
ID: "",
Name: "Mary",
},
SetupMocks: func(m *Mocks) {
m.DB.EXPECT().Put("", &user.User{
ID: "",
Name: "Mary",
}).Return(errors.New("missing ID"))
},
ExpectedError: user.ErrSavingUser,
},
}
ensure.RunTableByIndex(table, func(ensure Ensure, i int) {
entry := table[i]
err := entry.Subject.Save(entry.Mocks.Context, entry.Input)
ensure(err).IsError(entry.ExpectedError)
})
}
// mocksets/mocksets.go
type DefaultMocks struct {
// Tag suppresses warning when it isn't used in the Subject
Context *mockctx.MockContext `ensure:"ignoreunused"`
}
// mockctx/mockctx.go
type MockContext struct { context.Context }
// NEW method allows creating a MockContext, and is automatically called by ensure.
func (*MockContext) NEW() *MockContext {
return &MockContext{Context: context.Background()}
}