Ghost does test assertions in Go.
This is early-stage software, and some breaking changes are still expected.
- Utilities that stay out of your way: Ghost extends standard library testing without trying to replace it. Set up your test in one line of code, and you're good to go.
- Generics-friendly logic: All built-in assertions were designed with generics in mind. Shift failures left, and let your compiler tell you when assertions aren't set up correctly.
- Test output that knows your code: Ghost uses AST parsing to read your source code and print out the most useful information for you.
- Assertions that can be composed, extended, and reused: Easily write custom test assertions that are as simple to use as the built-ins. Every assertion can transformed to express whatever you need to.
Start each test by calling g := ghost.New(t)
. Then, write your assertions:
func TestMyFunc(t *testing.T) {
g := ghost.New(t)
got, err := MyFunc()
g.NoError(err)
g.MustNot(be.Nil(got))
g.Should(be.Equal(got.SomeString, "my value"))
g.Should(be.SliceLen(got.SomeSlice, 3))
}
func TestMyFunc_error(t *testing.T) {
g := ghost.New(t)
got, err := MyFunc()
g.Should(be.Zero(got))
g.Should(be.ErrorEqual(err, "an error occurred"))
}
Ghost comes with four main checks: Should
, ShouldNot
, Must
, and MustNot
.
Should
and ShouldNot
check whether an assertion has succeeded, failing the
test otherwise. Like t.Error
, the test is allowed to proceed if the assertion
fails:
g.Should(be.Equal(got, want))
g.ShouldNot(be.Nil(val))
Both functions also return a boolean indicating whether the check was successful, allowing you to safely chain assertion logic:
if g.Should(be.SliceLen(mySlice, 1)) {
g.Should(be.Equal(mySlice[0], "foo"))
}
Must
and MustNot
work similarly, but end test execution if the assertion
does not pass, analogous to t.Fatal
:
g.Must(be.True(ok))
g.MustNot(be.Nil(val))
For convenience, a NoError
check is also available, which fails and ends test
execution for non-nil errors:
g.NoError(err)
// Equivalent to:
g.MustNot(be.Error(err))
An assertion is any function that returns a ghost.Result
.
A set of standard assertions are available in github.com/rliebz/ghost/be.
These cover common use cases, such as simple and deep equality, slice/map/string operations, error and panic handling, and JSON equality.
g.Should(be.True(true))
g.ShouldNot(be.False(true))
g.Should(be.Equal(1+1, 2))
g.Should(be.DeepEqual([]string{"a", "b"}, []string{"a", "b"}))
g.Should(be.SliceContaining([]int{1, 2, 3}, 2))
g.Should(be.StringContaining("foobar", "foo"))
var err error
g.NoError(err)
g.Must(be.Nil(err))
g.MustNot(be.Error(err))
err = errors.New("test error: oh no")
g.Should(be.Error(err))
g.Should(be.ErrorEqual(err, "test error: oh no"))
g.Should(be.ErrorContaining(err, "oh no"))
g.Should(be.JSONEqual(`{"b": 1, "a": 0}`, `{"a": 0, "b": 1}`))
g.ShouldNot(be.JSONEqual(`{"a":1}`, `{"a":2}`))
For the full list available, see the documentation.
Ghost allows assertions to be composed into powerful expressions.
One composer is be.Eventually
, which retries an assertion over time until
it either succeeds or times out:
g.Should(be.Eventually(func() ghost.Result {
return be.True(val.IsSettled(ctx))
}, 3*time.Second, 100*time.Millisecond))
Another composer is be.Not
, which negates the result of an assertion:
g.Should(be.Not(be.True(ok)))
g.Must(be.Not(be.Nil(val)))
While be.Not
in a simple assertion would simply be a more verbose version of
of ShouldNot
or MustNot
, the real benefit becomes obvious when you combine
composers together:
g.Should(be.Eventually(func() ghost.Result {
return be.Not(be.Equal(a, b))
}, 3*time.Second, 100*time.Millisecond))
For details on other composers such as be.Any
or be.All
, see the godoc.
Custom assertions are easy to write and easy to use.
A very basic custom assertion might look like this:
func BeThirteen(i int) ghost.Result {
return ghost.Result{
Ok: i == 13,
Message: fmt.Sprintf("value is %d", i),
}
}
Custom assertions can be used with the built-in checks:
i := 13
g.Should(BeThirteen(i))
One of the key features that makes test output readable is understanding the AST to be able to print better failure messages. Use ghostlib to pretty print the AST values of assertion arguments:
func BeThirteen(i int) ghost.Result {
args := ghostlib.ArgsFromAST(i)
return ghost.Result{
Ok: i == 13,
Message: fmt.Sprintf("%v is %d", args[0], i),
}
}
And instantly get helpful, descriptive error messages:
g.Should(BeThirteen(myInt)) // "myInt is 0"
g.Should(BeThirteen(5 + 6)) // "5 + 6 is 11"
If you expect your code to panic, it is better to assert that the value passed
to panic
has the properties you expect, rather than to make an assumption
that the panic you encountered is the panic you were expecting. Ghost can be
combined with defer
/recover
to access the full expressiveness of test
assertions:
defer func() {
var err error
g.Must(be.AssignedAs(recover(), &err))
g.Should(be.ErrorEqual(err, "a specific error occurred"))
}()
doStuff()
Go's testing
package is fantastic; Ghost doesn't try to do anything that the
standard library already does.
Test suites, mocking, logging, and non-assertion failures are all out of scope.
Some testing libraries lock you into stopping test execution on assertion failure. Ghost makes it easy to switch between both, and doesn't make you change the way you set tests up based on which of the two you use:
g := ghost.New(t) // universal test setup
// ...
g.Should(be.Equal(myInt, 13)) // soft assertion
g.Must(be.True(ok)) // hard assertion
Arguments to assertions should go in an intuitive, predictable order. By convention:
- "Got" comes before "want".
- "Haystack" comes before "needle".
- All other arguments come last.
Go's testing
package is fantastic; Ghost doesn't try to do anything that the
standard library already does.
Test suites, mocking, logging, and non-assertion failures are all out of scope.