/justest

Go testing framework with extra sugar!

Primary LanguageGoMIT LicenseMIT

justest

Maintainer GoVersion GoDoc GoReportCard

Go testing framework with extra sugar

This Go testing framework has the following goals:

  • Play nice with go test
  • Provide a fluent API for making assertions
  • Provide a succinct yet informative error information on failures
  • Make testing easier to read and write

Attribution

This library is heavily inspired by Gomega and Ginkgo, two excellent Go testing libraries that I've used extensively in the past. If you need a full standalone (see below) BDD testing framework, those are highly recommended.

The reason Justest was created is because Gomega and Ginkgo do not play great with go test (though Gomega does have a go test integration, it's still primarily meant to be used with Ginkgo) whereas Justest is meant all along to be used with go test.

Usage

package my_test

import (
	"fmt"
	"regexp"
	"testing"
	"time"

	. "github.com/arikkfir/justest"
)

func TestSomething(t *testing.T) {
	
	// Simple assertions
	With(t).VerifyThat(1).Will(BeBetween(0, 2)).Now()
	With(t).VerifyThat("").Will(BeEmpty()).Now()
	With(t).VerifyThat([]int{1, 2, 3}).Will(BeEmpty()).Now() // <-- This will fail!
	With(t).VerifyThat(1).Will(BeGreaterThan(0)).Now()
	With(t).VerifyThat(1).Will(BeLessThan(2)).Now()
	With(t).VerifyThat("abc").Will(BeNil()).Now() // <-- This will fail!
	With(t).VerifyThat(1).Will(EqualTo(1)).Now()
	With(t).VerifyThat("abc").Will(EqualTo("def")).Now() // <-- This will fail!

	// Assert success or failure of a function (functions can have any set of return values or none at all)
	succeedingFunc := func() (string, error) { return "abc", nil }
	With(t).VerifyThat(succeedingFunc).Will(Succeed()).Now() // <-- Will succeed since error return value is nil
	With(t).VerifyThat(succeedingFunc).Will(Fail()).Now()    // <-- Will fail since it expects error return value to be non-nil
	failingFunc := func() (string, error) { return "", fmt.Errorf("error") }
	With(t).VerifyThat(failingFunc).Will(Succeed()).Now() // <-- Will fail since error return value is not nil
	With(t).VerifyThat(failingFunc).Will(Fail()).Now()    // <-- Will succeed since it expects error return value to be non-nil

	// Assert negation of another assertion
	With(t).VerifyThat(1).Will(Not(EqualTo(2))).Now()
	
	// Assert something will **eventually** match
	// It will stop when the function succeeds (no assertion failure) or when time runs out
	With(t).VerifyThat(func(t T) {

		// Will be invoked every 100ms until either it no longer fails or until time runs out (10s)
		With(t).VerifyThat(2).Will(EqualTo(2)).Now()

	}).Will(Succeed()).Within(10*time.Second, 100*time.Millisecond)

	// Assert something will **repeatedly** match for a certain amount of time
	// It will stop on the first time the function fails
	With(t).VerifyThat(func(t T) {

		// Will be invoked every 100ms until either it fails or until time runs out (10s)
		With(t).VerifyThat(2).Will(EqualTo(2)).Now()

	}).Will(Succeed()).For(10*time.Second, 100*time.Millisecond)

	// Assert on text patterns
	With(t).VerifyThat("abc").Will(Say("^a*c$")).Now()
	With(t).VerifyThat("abc").Will(Say(regexp.MustCompile("^a*c$"))).Now()
	With(t).VerifyThat([]byte("abc")).Will(Say("^a*c$")).Now()
}

Custom matchers

You can easily create your own matchers by implementing the Matcher interface:

package my_test

import (
	"reflect"

	. "github.com/arikkfir/justest"
)

var (
	myValueExtractor = NewValueExtractor(ExtractSameValue)
)

// BeSuperDuper returns a matcher that will ensure that each actual value passed to "With(t).VerifyThat(...)" will be either
// "super duper" or "extra super duper", depending on the value of the `extraDuper` parameter.
func BeSuperDuper(extraDuper bool) Matcher {
	return MatcherFunc(func(t T, actuals ...any) {
		GetHelper(t).Helper()
		for _, actual := range actuals {
			v := myValueExtractor.MustExtractValue(t, actual) // This is optional, but recommended, see value extraction below
			if extraDuper {
				// Fail if it's not EXTRA super-duper
				if v.(string) != "extra super duper" {
					t.Fatalf("Value '%s' is not extra super-duper!", v)
				}
			} else {
				// Fail if it's not super-duper
				if v.(string) != "super duper" {
					t.Fatalf("Value '%s' is not super-duper!", v)
				}
			}
		}
	})
}

Builtin matchers

Matcher Name Description
BeBetween(min, max) Checks that all given values are between a minimum and maximum value
BeEmpty() Checks that all given values are empty
BeGreaterThan(min) Checks that all given values are greater than a minimum value
BeLessThan(max) Checks that all given values are less than a maximum value
BeNil() Checks that all given values are nil
EqualTo(expected) Checks that all given values are equal to their corresponding expected value
Fail() Checks that the last given value is a non-nil error instance
Not() Checks that the given matcher fails
Say() Checks that all given values match the given regular expression
Succeed() Checks that the last given value is either nil or not an error instance

Contributing

Please do 👌 💪 !

See CONTRIBUTING.md for more information 🙏