package
0.0.21
Repository: https://github.com/tkrop/go-testing.git
Documentation: pkg.go.dev

# README

Package testing/test

The goal of this package is to provide a small framework to isolate the test execution and safely check whether a test succeeds or fails as expected. In combination with the mock package it ensures that a test finishes reliably and reports its failure even if a system under test is spawning go-routines.

Example usage

Use the following example to intercept and validate a panic using the isolated test environment.

func TestUnit(t *testing.T) {
    test.Run(test.Success, func(t test.Test){
        // Given
        mocks := mock.NewMocks(t).Expect(
            test.Panic("fail"),
        )

        // When
        panic("fail")
    })(t)

    // Then
    ...
}

Isolated parameterized parallel test runner

The test framework supports to run isolated, parameterized, parallel tests using a lean test runner. The runner can be instantiated with a single test parameter set (New), a slice of test parameter sets (Slice), or a map of test case name to test parameter sets (Map - preferred pattern). The test is started by Run that accepts a simple test function as input, using a test.Test interface, that is compatible with most tools, e.g. gomock.

func TestUnit(t *testing.T) {
    test.New|Slice|Map(t, testParams).
        /* Filter("test-case-name", false|true). */
        Run|RunSeq(func(t test.Test, param UnitParams){
            // Given

            // When

            // Then
        }).Cleanup(func(){
            // clean test resources
        })
}

This creates and starts a lean test wrapper using a common interface, that isolates test execution and intercepts all failures (including panics), to either forward or suppress them. The result is controlled by providing a test parameter of type test.Expect (name expect) that supports Failure (false) and Success (true - default).

Similar a test case name can be provided using type test.Name (name name - default value unknown-%d) or as key using a test case name to parameter set mapping.

Note: See Parallel tests requirements for more information on requirements in parallel parameterized tests. If parallel parameterized test are undesired, RunSeq can be used to enforce a sequential test execution.

It is also possible to select a subset of tests for execution by setting up a Filter using a regular expression to match or filter by the normalized test name.

Isolated in-test environment setup

It is also possible to isolate only a single test step by setting up a small test function that is run in isolation.

func TestUnit(t *testing.T) {
    test.Map(t, testParams).
        Run|RunSeq(func(t test.Test, param UnitParams){
            // Given

            // When
            test.InRun(test.Failure, func(t test.Test) {
                ...
            })(t)

            // Then
        })
}

Manual isolated test environment setup

If the above pattern is not sufficient, you can create your own customized parameterized, parallel, isolated test wrapper using the basic abstraction test.Run(test.Success|Failure, func (t test.Test) {}):

func TestUnit(t *testing.T) {
    t.Parallel()

    for name, param := range testParams {
        name, param := name, param
        t.Run(name, test.Run(param.expect, func(t test.Test) {
            t.Parallel()

            // Given

            // When

            // Then
        }))
    }
}

Or the interface of the underlying test.Tester:

func TestUnit(t *testing.T) {
    t.Parallel()

    test.NewTester(t, test.Success).Run(func(t test.Test){
        // Given

        // When

        // Then
    })(t)
}

But this should usually be unnecessary.

Isolated failure/panic validation

Besides just capturing the failure in the isolated test environment, it is also very simple possible to validate the failures/panics using the self installing validator that is tightly integrated with the mock framework.

func TestUnit(t *testing.T) {
    test.Run(func(t test.Test){
        // Given
        mock.NewMocks(t).Expect(mock.Setup(
            test.Errorf("fail"),
            test.Fatalf("fail"),
            test.FailNow(),
            test.Panic("fail"),
        ))

        // When
        t.Errorf("fail")
        ...
        // And one of the following
        t.Fatalf("fail")
        t.FailNow()
        panic("fail")

        // Then
    })(t)
}

Note: To enable panic testing, the isolated test environment is recovering from all panics by default and converting it in a fatal error message. This is often most usable and sufficient to fix the issue. If you need to discover the source of the panic, you need to spawn a new unrecovered go-routine.

Hint: gomock uses very complicated reporting patterns that are hard to recreate. Do not try it.

Out-of-the-box test patterns

Currently, the package supports the following out-of-the-box test pattern for testing of main-methods of commands.

testMainParams := map[string]test.MainParams{
    "no mocks": {
        Args:     []string{"mock"},
        Env:      []string{},
        ExitCode: 0,
    },
}

func TestMain(t *testing.T) {
    test.Map(t, testMainParams).Run(test.TestMain(main))
}

The pattern executes a the main-method in a separate process that allows to setup the command line arguments (Args) as well as to modify the environment variables (Env) and to capture and compare the exit code.

Note: the general approach can be used to test any code calling os.Exit, however, it is focused on testing methods without arguments parsing command line arguments, i.e. in particular func main() { ... }.

# Functions

No description provided by the author
EqCall creates a new call matcher that allows to match calls by translating them to the string containing the core information instead of using the standard matcher using [reflect.DeepEquals] that fails for the contained actions.
EqError creates a new error matcher that allows to match either the error or alternatively the string describing the error.
Errorf creates a validation method call setup for `Errorf`.
FailNow creates a validation method call setup for `FailNow`.
Fatalf creates a validation method call setup for `Fatalf`.
Find returns the first value of a parameter field from the given list of field names with a type matching the default value type.
InRun creates an isolated test environment for the given test function with given expectation.
Map creates a new parallel test runner with given test parameter sets provided as a test case name to parameter sets mapping.
MissingCalls creates an expectation for all missing calls.
New creates a new parallel test runner with given parameter sets, i.e.
NewAccessor creates a generic builder/accessor for a given target struct.
NewBuilder creates a generic builder for a target struct type.
NewErrUnknownParameterType creates a new unknown parameter type error.
NewTester creates a new minimal test context based on the given `go-test` context.
NewValidator creates a new test validator for validating error messages and panics created during test execution.
Panic creates a validation method call setup for a panic.
Run creates an isolated (by default) parallel test environment running the given test function with given expectation.
RunSeq creates an isolated, test environment for the given test function with given expectation.
Slice creates a new parallel test runner with given test parameter sets provided as a slice.
TestExpect resolves the test case expectation from the parameter set.
TestMain creates a test function that runs the given `main`-method in a separate test process to allow capturing the exit code and checking it against the expectation.
TestName returns the normalized test case name for the given name and given parameter set.
UnexpectedCall creates expectation for unexpected calls.

# Constants

Failure used to express that a test is supposed to fail.
Flag to run test by default sequential instead of parallel.
Success used to express that a test is supposed to succeed.

# Variables

ErrUnknownParameterType is an error for unknown parameter types.

# Structs

MainParams provides the test parameters for testing a `main`-method.
Recorder a test failure validator recorder.
Tester is a test isolation environment based on the `Test` abstraction.
Validator a test failure validator based on the test reporter interface.

# Interfaces

Builder is a generic interface that allows you to access and modify unexported fields of a (pointer) struct by field name.
Cleanuper defines an interface to add a custom mehtod that is called after the test execution to cleanup the test environment.
Reporter is a minimal interface for abstracting test report methods that are needed to setup an isolated test environment for GoMock and Testify.
Runner is a generic test runner interface.
Test is a minimal interface for abstracting test methods that are needed to setup an isolated test environment for GoMock and Testify.

# Type aliases

No description provided by the author
No description provided by the author