Intro

Testify: A toolkit with common assertions and mocks that plays nicely with the standard library

Official website link


Assert Example

The testing target function Add:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func Add(a, b int) int {
return a - b // Intentionally
}

func main() {
fmt.Println("Hello, World!__")
}

Table-structured testing using assert like below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
// Define test casess
tests := []struct {
a, b int
expected int
}{
{2, 3, 5},
{0, 0, 0},
{-1, 1, 0},
{-1, -1, -2},
}

// Iterate over test cases
for _, test := range tests {
result := Add(test.a, test.b)
// Use testify's assert package for assertions
assert.Equal(t, test.expected, result, "Expected %d + %d to equal %d", test.a, test.b, test.expected)
}
}

Run the test command and it will show rich and structured testing results

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Go test

:' --- FAIL: TestAdd (0.00s)
add_test.go:25:
Error Trace: C:/Users/24743/Desktop/go_test/add_test.go:25
Error: Not equal:
expected: 5
actual : -1
Test: TestAdd
Messages: Expected 2 + 3 to equal 5
add_test.go:25:
Error Trace: C:/Users/24743/Desktop/go_test/add_test.go:25
Error: Not equal:
expected: 0
actual : -2
Test: TestAdd
Messages: Expected -1 + 1 to equal 0
add_test.go:25:
Error Trace: C:/Users/24743/Desktop/go_test/add_test.go:25
Error: Not equal:
expected: -2
actual : 0
Test: TestAdd
Messages: Expected -1 + -1 to equal -2
FAIL
exit status 1
FAIL go_test 0.179s'


Suite example

The testify suite in Go is used to organize and structure your tests in a more modular and reusable way.

  • Test Suite Structure: By using suite.Suite from testify, all test cases for the Calculator are grouped into a single CalculatorTestSuite. This allows for a clear separation of tests related to the Calculator while promoting reusability and consistency across multiple tests.

  • Setup and Teardown: The SetupTest and TearDownTest methods allow for reusable setup and cleanup logic, avoiding repetitive code in individual test cases. This makes the test suite more maintainable and modular.

  • Modularity with Testify: Using assert from testify provides reusable and concise assertions. This avoids the need for verbose error handling and enhances test readability.

Code block obtained from LINK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

// Calculator is a simple calculator with basic operations
type Calculator struct{}

func (c *Calculator) Add(a, b int) int {
return a + b
}

func (c *Calculator) Subtract(a, b int) int {
return a - b
}

// CalculatorTestSuite is a suite to test the Calculator implementation
type CalculatorTestSuite struct {
suite.Suite
calculator *Calculator
}

// SetupTest initializes the Calculator instance before each test
func (suite *CalculatorTestSuite) SetupTest() {
suite.calculator = &Calculator{}
}

// TearDownTest performs cleanup after each test
func (suite *CalculatorTestSuite) TearDownTest() {
fmt.Println("Tearing down after each test")
}

// TestAdd tests the Add method of the Calculator
func (suite *CalculatorTestSuite) TestAdd() {
result := suite.calculator.Add(3, 5)
assert.Equal(suite.T(), 8, result, "Adding 3 and 5 should equal 8")
}

// TestSubtract tests the Subtract method of the Calculator
func (suite *CalculatorTestSuite) TestSubtract() {
result := suite.calculator.Subtract(10, 4)
assert.Equal(suite.T(), 6, result, "Subtracting 4 from 10 should equal 6")
}

// TestSuite runs the CalculatorTestSuite
func TestSuite(t *testing.T) {
suite.Run(t, new(CalculatorTestSuite))
}

/*
$ go test ./suite_example/
ok go_test/suite_example 0.243s
*/

Mock Example

  • Mock Service Behavior: The line mockSvc.On("GetData", "123").Return("mocked data", nil) mocks the behavior of GetData("123"), returning "mocked data" with no error.

  • Test ProcessData: The test checks whether calling ProcessData(mockSvc, "123") returns the expected output: Expected output: "Processed: mocked data".

  • Assertions:

    assert.NoError verifies there are no errors. assert.Equal checks if the result equals "Processed: mocked data".

Verify Mock Expectations: mockSvc.AssertExpectations(t) ensures the mock behavior (calling GetData with the argument "123") matches the defined expectations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

// Service is the interface we want to mock
type Service interface {
GetData(id string) (string, error)
}

// MockService is a struct that embeds testify's mock struct
type MockService struct {
mock.Mock
}

// Mock implementation of GetData
func (m *MockService) GetData(id string) (string, error) {
args := m.Called(id)
return args.String(0), args.Error(1)
}

// Function to test using the mock
func ProcessData(svc Service, id string) (string, error) {
data, err := svc.GetData(id)
if err != nil {
return "", err
}
return "Processed: " + data, nil
}

// Test with mock
func TestProcessData(t *testing.T) {
mockSvc := new(MockService)
mockSvc.On("GetData", "123").Return("mocked data", nil)

result, err := ProcessData(mockSvc, "123")

assert.NoError(t, err)
assert.Equal(t, "Processed: mocked data", result)

// Assert that the expectations were met
mockSvc.AssertExpectations(t)
}

/*$ go test
PASS
ok go_test/mock_example 0.186s*/

When

When to Use suite:

* Multiple Test Cases: Use suite when you have several related test cases that require similar setup, teardown, or shared dependencies.
* Shared Setup/Teardown: When you need to initialize resources (e.g., database, external services, mock objects) before running the tests and clean them up after.
* Complex Testing Scenarios: Use suite when you have more complex tests that involve multiple test cases interacting with mock objects or other dependencies in different ways.
* Organizing Test Cases: When you want to logically group your tests, improve test readability, and reduce repetition by centralizing common setup.

When to Use mock:

* Isolated Unit Tests: Use mock when you want to mock out individual dependencies in a specific test. For example, when you want to mock an external service, database, or API.
Simulating Return Values and Errors: When you need to return specific values or errors from a method that you don’t want to actually execute during the test.
* Simple Tests: When you don’t need extensive setup/teardown and just want to mock a method for one or two test cases.