Home

Awesome

<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->

Mentioned in Awesome Go GoDoc Build Status Go Report Card codecov

testcase

The testcase package provides tooling to apply BDD testing conventions.

If you use Go1.20, then due to the changes in the stdlib testing package, please upgrade testcase to version >= v0.131.0.

Features

Guide

testcase is a powerful TDD Tooling that requires discipline and understanding of the fundamentals of testing. If you are looking for a guide that helps streamline your knowledge on the topics, then please consider reading the below-listed articles.

Official API Documentation

If you already use the framework, and you just won't pick an example, you can go directly to the API documentation that is kept in godoc format.

Getting Started / Example

Examples kept in godoc format. Every exported functionality aims to have examples provided in the official documentation.

A Basic examples:

func Test_withTestcaseT(t *testing.T) {
	tc := testcase.NewT(t, nil)
	
	tc.Must.True(true)
	_ = tc.Random.String()
}

func Test_withTestcaseSpec(t *testing.T) {
	s := testcase.NewSpec(t)
	
	s.Test("", func(t *testcase.T) {
		t.Must.True(true)
		_ = tc.Random.String()
	})
}

An example with scoped test variables:

package main

import (
	"testing"

	"go.llib.dev/testcase"
)

func TestMyFunc(t *testing.T) {
	s := testcase.NewSpec(t)
	s.NoSideEffect()

	var (
		input = testcase.Let[string](s, nil)
    )
	act := func(t *testcase.T) string {
		return MyFunc(input.Get(t))
	}

	s.When("input is all lowercase", func(s *testcase.Spec) {
		// testing scopes without the input being defined
		// will warn the dev about the missing specification.
		input.LetValue(s, "all lowercase")

		s.Then("it is expected to ...", func(t *testcase.T) {
			t.Must.Equal("all lowercase", act(t))
		})
	})

	s.When("input is all upcase", func(s *testcase.Spec) {
		input.LetValue(s, "ALL UPCASE")

		s.Then("it is expected to ...", func(t *testcase.T) {
			t.Must.Equal("all upcase", act(t))
		})
	})
}

Modules

Summary

DRY

testcase provides a way to express common Arrange, Act sections for the Asserts with a DRY principle in mind.

Then adding test edge case to the testing suite becomes easier, as it will have a concrete place where it must be placed.

And if during the creation of the specification, an edge case turns out to be YAGNI, it can be noted, so visually it will be easier to see what edge case is not specified for the given subject.

The value it gives is that to build a test for a certain edge case, the required mental model size to express the context becomes smaller, as you only have to focus on one Arrange at a time, until you fully build the bigger picture.

It also implicitly visualize the required mental model of your production code by the nesting. You can read more on that in the nesting section.

Modularization

On top of the DRY convention, any time you need to Arrange a common scenario about your projects domain event, you can modularize these setup blocks in a helper function.

This helps the readability of the test while keeping the need for mocks to the minimum as possible for a given test. As a side effect, integration tests can become low-hanging fruit for the project.

e.g.:

package mypkg_test

import (
	"testing"

	"my/project/mypkg"


	"go.llib.dev/testcase"

	. "my/project/testing/pkg"
)

func TestMyTypeMyFunc(t *testing.T) {
	s := testcase.NewSpec(t)
	
	myUser := GivenWeHaveUser(s)
	// .. other givens

	myType := testcase.Let(s, func(t *testcase.T) *mypkg.MyType {
		return &mypkg.MyType{}
	})

	s.Describe(`.MyFunc`, func(s *testcase.Spec) {
		act := func(t *testcase.T) { myType.Get(t).MyFunc(myUser.Get(t)) }

		s.Then(`edge case description`, func(t *testcase.T) {
			act(t)
		})
	})
}

Stability

Case Study About testcase Package Origin

Reference Project