Skip to content

FollowTheProcess/test

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

test

License Go Reference Go Report Card GitHub CI codecov

A lightweight test helper package 🧪

Project Description

test is my take on a handy, lightweight Go test helper package. Inspired by matryer/is, earthboundkid/be and others.

It provides a lightweight, but useful, extension to the std lib testing package with a friendlier and hopefully intuitive API. You definitely don't need it, but might find it useful anyway 🙂

Installation

go get github.com/FollowTheProcess/test@latest

Usage

test is as easy as...

func TestSomething(t *testing.T) {
    test.Equal(t, "hello", "hello") // Obviously fine
    test.Equal(t, "hello", "there") // Fails

    test.NotEqual(t, 42, 27) // Passes, these are not equal
    test.NotEqual(t, 42, 42) // Fails

    test.NearlyEqual(t, 3.0000000001, 3.0) // Look, floats handled easily!

    err := doSomething()
    test.Ok(t, err) // Fails if err != nil
    test.Err(t, err) // Fails if err == nil

    test.True(t, true) // Passes
    test.False(t, true) // Fails
}

Add Additional Context

test provides a number of options to decorate your test log with useful context:

func TestDetail(t *testing.T) {
    test.Equal(t, "apples", "oranges", test.Title("Fruit scramble!"), test.Context("Apples are not oranges!"))
}

Will get you an error log in the test that looks like this...

--- FAIL: TestDemo (0.00s)
    test_test.go:501: 
        Fruit scramble!
        ---------------
        
        Got:    apples
        Wanted: oranges
        
        (Apples are not oranges!)
        
FAIL

Non Comparable Types

test uses generics under the hood for most of the comparison, which is great, but what if your types don't satisfy comparable. We also provide test.EqualFunc and test.NotEqualFunc for those exact situations!

These allow you to pass in a custom comparator function for your type, if your comparator function returns true, the types are considered equal.

func TestNonComparableTypes(t *testing.T) {
    // Slices do not satisfy comparable
    a := []string{"hello", "there"}
    b := []string{"hello", "there"}
    c := []string{"general", "kenobi"}

    // Custom function, returns true if things should be considered equal
    sliceEqual := func(a, b, []string) { return true } // Cheating

    test.EqualFunc(t, a, b, sliceEqual) // Passes

    // Can also use any function here
    test.EqualFunc(t, a, b, slices.Equal) // Also passes :)

    test.EqualFunc(t, a, c, slices.Equal) // Fails
}

You can also use this same pattern for custom user defined types, structs etc.

Table Driven Tests

Table driven tests are great! But when you test errors too it can get a bit awkward, you have to do the if (err != nil) != tt.wantErr thing and I personally always have to do the boolean logic in my head to make sure I got that right. Enter test.WantErr:

func TestTableThings(t *testing.T) {
    tests := []struct {
        name    string
        want    int
        wantErr bool
    }{
        {
            name:    "no error",
            want:    4,
            wantErr: false,
        },
        {
            name:    "yes error",
            want:    4,
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := SomeFunction()
    
            test.WantErr(t, err, tt.wantErr)
            test.Equal(t, got, tt.want)
        })
    }
}

Which is basically semantically equivalent to:

func TestTableThings(t *testing.T) {
    tests := []struct {
        name    string
        want    int
        wantErr bool
    }{
        {
            name:    "no error",
            want:    4,
            wantErr: false,
        },
        {
            name:    "yes error",
            want:    4,
            wantErr: true,
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := SomeFunction()
    
            if tt.wantErr {
                test.Err(t, err)
            } else {
                test.Ok(t, err)
            }
            test.Equal(t, got, tt.want)
        })
    }
}

Capturing Stdout and Stderr

We've all been there, trying to test a function that prints but doesn't accept an io.Writer as a destination 🙄.

That's where test.CaptureOutput comes in!

func TestOutput(t *testing.T) {
    // Function that prints to stdout and stderr, but imagine this is defined somewhere else
    // maybe a 3rd party library that you don't control, it just prints and you can't tell it where
    fn := func() error {
        fmt.Fprintln(os.Stdout, "hello stdout")
        fmt.Fprintln(os.Stderr, "hello stderr")

        return nil
    }

    // CaptureOutput to the rescue!
    stdout, stderr := test.CaptureOutput(t, fn)

    test.Equal(t, stdout, "hello stdout\n")
    test.Equal(t, stderr, "hello stderr\n")
}

Under the hood CaptureOutput temporarily captures both streams, copies the data to a buffer and returns the output back to you, before cleaning everything back up again.

See Also

Credits

This package was created with copier and the FollowTheProcess/go_copier project template.

About

A lightweight test helper package 🧪

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages