How can I do test setup using the testing package in Go

Unit TestingGo

Unit Testing Problem Overview


How can I do overall test setup processing which sets the stage for all the tests when using the testing package?

As an example in Nunit there is a [SetUp] attribute.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}

Unit Testing Solutions


Solution 1 - Unit Testing

Starting with Go 1.4 you can implement setup/teardown (no need to copy your functions before/after each test). The documentation is outlined here in the Main section:

> TestMain runs in the main goroutine and can do whatever setup and > teardown is necessary around a call to m.Run. It should then call > os.Exit with the result of m.Run

It took me some time to figure out that this means that if a test contains a function func TestMain(m *testing.M) then this function will be called instead of running the test. And in this function I can define how the tests will run. For example I can implement global setup and teardown:

> func TestMain(m *testing.M) { > setup() > code := m.Run() > shutdown() > os.Exit(code) > }

A couple of other examples can be found here.

> The TestMain feature added to Go’s testing framework in the latest > release is a simple solution for several testing use cases. TestMain > provides a global hook to perform setup and shutdown, control the > testing environment, run different code in a child process, or check > for resources leaked by test code. Most packages will not need a > TestMain, but it is a welcome addition for those times when it is > needed.

Solution 2 - Unit Testing

This can be achieved by putting a init() function in the myfile_test.go file. This will be run before the init() function.

// myfile_test.go
package main

func init() {
     /* load test data */
}

The myfile_test.init() will be called before the package init() function.

Solution 3 - Unit Testing

Given a simple function to unit test:

package math

func Sum(a, b int) int {
    return a + b
}

You can test it with a setup function that returns teardown function. And after calling setup() you can make a deferred call to teardown().

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
	t.Log("setup test case")
	return func(t *testing.T) {
		t.Log("teardown test case")
	}
}

func setupSubTest(t *testing.T) func(t *testing.T) {
	t.Log("setup sub test")
	return func(t *testing.T) {
		t.Log("teardown sub test")
	}
}

func TestAddition(t *testing.T) {
    cases := []struct {
		name     string
    	a        int
	    b        int
		expected int
	}{
		{"add", 2, 2, 4},
		{"minus", 0, -2, -2},
		{"zero", 0, 0, 0},
	}

	teardownTestCase := setupTestCase(t)
	defer teardownTestCase(t)

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			teardownSubTest := setupSubTest(t)
			defer teardownSubTest(t)

			result := Sum(tc.a, tc.b)
			if result != tc.expected {
				t.Fatalf("expected sum %v, but got %v", tc.expected, result)
			}
		})
	}
}

Go testing tool will report the logging statements in the shell console:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
	math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
    	math_test.go:13: setup sub test
    	math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
    	math_test.go:13: setup sub test
    	math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
    	math_test.go:13: setup sub test
    	math_test.go:15: teardown sub test
	math_test.go:8: teardown test case
PASS
ok  	github.com/kare/go-unit-test-setup-teardown	0.010s
% 

You can pass some additional parameters to setup/teardown with this approach.

Solution 4 - Unit Testing

The Go testing framework doesn't have anything equivalent to NUnit's SetUp attribute (marking a function to be called before each test in the suite). There are a few options though:

  1. Simply call your SetUp function from each test where it is needed.

  2. Use an extension to Go's testing framework that implements xUnit paradigms and concepts. Three strong options come to mind:

Each of these libraries encourage you to organize your tests into suites/fixtures similar to other xUnit frameworks, and will call the setup methods on the suite/fixture type before each of the Test* methods.

Solution 5 - Unit Testing

Typically, tests in go aren't written in the same style as other languages. Often, there's relatively fewer test functions, but each contains a table-driven set of test cases. See this article written by one of the Go team.

With a table-driven test, you simply put any setup code before the loop that executes the individual test-cases specified in the table, and put any cleanup code afterwards.

If you still have shared setup code between test functions, you can extract the shared setup code into a function, and use a sync.Once if it's important that it's executed exactly once (or as another answer suggests, use init(), but this has the disadvantage that the setup will be done even if the test cases aren't run (perhaps because you've limited the test cases by using go test -run <regexp>.)

I'd say if you think you need shared setup between different tests that gets executed exactly once you should have a think if you really need it, and if a table-driven test wouldn't be better.

Solution 6 - Unit Testing

With the following template, you can make a one line call in each TestMethod that does both setup and tear-down.

func setupTest() func() {
	// Setup code here

	// tear down later
	return func() {
		// tear-down code here
	}
}

func TestMethod(t *testing.T) {
	defer setupTest()()
    // asserts, ensures, requires... here
}

Solution 7 - Unit Testing

In case someone is looking an alternative of @BeforeEach (which runs before each test in a test file) and @AfterEach (which runs after test in a test file), here's a helper snippet.

func CreateForEach(setUp func(), tearDown func()) func(func()) {
	return func(testFunc func()) {
		setUp()
		testFunc()
		tearDown()
	}
}

You can use it like below with help of TestMain

var RunTest = CreateForEach(setUp, tearDown)

func setUp() {
   // SETUP METHOD WHICH IS REQUIRED TO RUN FOR EACH TEST METHOD
   // your code here
}

func tearDown() {
  // TEAR DOWN METHOD WHICH IS REQUIRED TO RUN FOR EACH TEST METHOD
  // your code here
}

fun TestSample(t *testing.T) {
  RunTest(func() {
    // YOUR CODE HERE
  })
}

also you can check: go-beforeeach

Solution 8 - Unit Testing

Shameless plug, I created https://github.com/houqp/gtest to help solve exactly this problem.

Here is a quick example:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

You can create as any test group you want within a package with each of them using a different set of setup/teardown routines.

Solution 9 - Unit Testing

You can use the testing package for test setup - which will set the stage for all tests and teardown - which will cleanup the stage after tests have run.

The below calculates the area of a rectangle:

package main

import (
	"errors"
	"fmt"
)

func area(height float64, width float64) (float64, error) {
	if height == width {
		fmt.Printf("Rectangle with given dimension is a square. Area is: %f\n", height * width)
		return height * width, nil
	} else if height <= 0 || width <= 0 {
		return 0, errors.New("Both dimensions need to be positive")
	} else {
		fmt.Printf("Area is: %f\n", height * width)
		return height * width, nil
	}
}

func main() {
	var length float64  = 4.0
	var breadth float64 = 5.0
	area(length, breadth)
}

This is the implementation for test setup and teardown using TestMain as Salvador Dali's explains. (Note that since v1.15 the TestMain function is no longer required to call os.Exit [ref])

package main

import (
	"log"
	"testing"
)

var length float64
var breadth float64

func TestMain(m *testing.M) {
    setup()
    m.Run() 
    teardown()
}

func setup() {
	length = 2.0
	breadth = 3.0
	log.Println("\n-----Setup complete-----")
}

func teardown() {
	length = 0
	breadth = 0
	log.Println("\n----Teardown complete----")
}

func TestAreaOfRectangle(t *testing.T) {
	val, err := area(length, breadth)	
	want := 6.0

	if val != want && err != nil {
		t.Errorf("Got %f and %v; Want %f and %v", val, err, want, nil)
	}
}

And this is the implementation for test setup and teardown using sub-tests:

package main

import "testing"

func TestInvalidRectangle(t *testing.T) {
	// setup
    var length float64 = -2.0
	var breadth float64 = 3.0
	t.Log("\n-----Setup complete for invalid rectangle-----")

	// sub-tests
    t.Run("invalid dimensions return value", func(t *testing.T) {
    	val, _ := area(length, breadth)
    	area := 0.0

    	if val != area {
    		t.Errorf("Got %f; Want %f", val, area)
    	}
    })

    t.Run("invalid dimensions message", func(t *testing.T) {
    	_, err := area(length, breadth)
    	want := "Both dimensions need to be positive"

    	if err.Error() != want {
			t.Errorf("Got error: %v; Want error: %v", err.Error(), want)
		}
    })

    // teardown
    t.Cleanup(func(){
        length = 0
        breadth = 0
        t.Log("\n----Teardown complete for invalid rectangle----")
    })
}

func TestRectangleIsSquare(t *testing.T) {
    var length float64 = 3.0
	var breadth float64 = 3.0
	t.Log("\n-----Rectangle is square setup complete-----")

    t.Run("valid dimensions value and message", func(t *testing.T) {
    	val, msg := area(length, breadth)
    	area := 9.0
    	if val != area && msg != nil {
    		t.Errorf("Got %f and %v; Want %f and %v", val, msg, area, nil)
    	}
    })

    t.Cleanup(func(){
        length = 0
        breadth = 0
        t.Log("\n----Rectangle is square teardown Complete----")
    })
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmiltonbView Question on Stackoverflow
Solution 1 - Unit TestingSalvador DaliView Answer on Stackoverflow
Solution 2 - Unit TestingmiltonbView Answer on Stackoverflow
Solution 3 - Unit TestingKare NuortevaView Answer on Stackoverflow
Solution 4 - Unit TestingJames HenstridgeView Answer on Stackoverflow
Solution 5 - Unit TestingPaul HankinView Answer on Stackoverflow
Solution 6 - Unit Testingshanmukh patelView Answer on Stackoverflow
Solution 7 - Unit TestingjknairView Answer on Stackoverflow
Solution 8 - Unit TestinghouqpView Answer on Stackoverflow
Solution 9 - Unit TestingSaurabhView Answer on Stackoverflow