testcase is an opinionated behavior-driven-testing library

Overview

Table of Contents

Mentioned in Awesome Go GoDoc Build Status Go Report Card codecov

testcase

The testcase package provides tooling to apply BDD testing conventions.

Guide

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

Official API Documentation

If you already use the framework, and you just want 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 example:

package main

import (
	"testing"

	"github.com/adamluzsi/testcase"
)

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

	var (
		message        = testcase.Var{Name: `message`}
		messageWrapper = s.Let(`message wrapper`, func(t *testcase.T) interface{} {
			return MessageWrapper{Message: message.Get(t).(string)}
		})
	)

	s.Describe(`#LookupMessage`, func(s *testcase.Spec) {
		subject := func(t *testcase.T) (string, bool) {
			return messageWrapper.Get(t).(MessageWrapper).LookupMessage()
		}

		s.When(`message is empty`, func(s *testcase.Spec) {
			message.LetValue(s, ``)

			s.Then(`it will return with "ok" as false`, func(t *testcase.T) {
				_, ok := subject(t)
				require.False(t, ok)
			})
		})

		s.When(`message has content`, func(s *testcase.Spec) {
			message.LetValue(s, fixtures.Random.String())

			s.Then(`it will return with "ok" as true`, func(t *testcase.T) {
				_, ok := subject(t)
				require.True(t, ok)
			})

			s.Then(`message received back`, func(t *testcase.T) {
				msg, _ := subject(t)
				require.Equal(t, message.Get(t), msg)
			})
		})
	})
}

Modules

  • httpspec
    • spec module helps you create HTTP API Specs.
  • fixtures
    • fixtures module helps you create random input values for testing

Summary

DRY

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

  • First you can define your Act section with a method under test as the subject of your test specification
    • The Act section invokes the method under test with the arranged parameters.
  • Then you can build the context of the Act by Arranging the inputs later with humanly explained reasons
    • The Arrange section initializes objects and sets the value of the data that is passed to the method under test.
  • And lastly you can define the test expected outcome in an Assert section.
    • The Assert section verifies that the action of the method under test behaves as expected.

Then adding an additional 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 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 functions.

This helps the readability of the test, while keeping the need of 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"


	"github.com/adamluzsi/testcase"

	. "my/project/testing/pkg"
)

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

	// high level Arrange helpers from my/project/testing/pkg
	SetupSpec(s)
	GivenWeHaveUser(s, `myuser`)
	// .. other givens

	myType := func() *mypkg.MyType { return &mypkg.MyType{} }

	s.Describe(`#MyFunc`, func(s *testcase.Spec) {
		var subject = func(t *testcase.T) { myType().MyFunc(t.I(`myuser`).(*mypkg.User)) } // Act

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

Stability

  • The package considered stable.
  • The package use rolling release conventions.
  • No breaking change is planned to the package exported API.
  • The package used for production development.
  • The package API is only extended if the practical use case proves its necessity.

Case Study About testcase Package Origin

Reference Project

You might also like...
Library created for testing JSON against patterns.

Gomatch Library created for testing JSON against patterns. The goal was to be able to validate JSON focusing only on parts essential in given test cas

A Go library help testing your RESTful API application

RESTit A Go micro-framework to help writing RESTful API integration test Package RESTit provides helps to those who want to write an integration test

HTTP load testing tool and library. It's over 9000!
HTTP load testing tool and library. It's over 9000!

Vegeta Vegeta is a versatile HTTP load testing tool built out of a need to drill HTTP services with a constant request rate. It can be used both as a

A WebDriver client and acceptance testing library for Go
A WebDriver client and acceptance testing library for Go

Agouti Agouti is a library for writing browser-based acceptance tests in Google Go. It provides Gomega matchers and plays nicely with Ginkgo or Spec.

Gostresslib - A golang library for stress testing.

GoStressLib A golang library for stress testing. Install go get github.com/tenhan/gostresslib Usage package main import ( "github.com/tenhan/gostres

Tesuto - a little library for testing against HTTP services

tesuto import "github.com/guregu/tesuto" tesuto is a little library for testing

A modern generic testing assertions library for Go

test test is a generics based testing assertions library for Go. There are two packages, test and must. test - assertions that mark the test for failu

Expressive end-to-end HTTP API testing made easy in Go

baloo Expressive and versatile end-to-end HTTP API testing made easy in Go (golang), built on top of gentleman HTTP client toolkit. Take a look to the

Simple Go snapshot testing
Simple Go snapshot testing

Incredibly simple Go snapshot testing: cupaloy takes a snapshot of your test output and compares it to a snapshot committed alongside your tests. If t

Comments
  • # randomized test order

    # randomized test order

    The goal of this feature to avoid implicit test ordering in the testing suite.

    A testing suite is also something that evolves with a project. New tests will be added, old tests will be deleted, and some will be updated to express changes in the business rules. To avoid problems as our test suites grow and change, it’s important to keep test cases independent.

    Test execution should be randomised, but the random order should be recreatable. The reason for this because developers can observe test randomly failing, and then using the test execution order seed, they can hunt down the test ordering based tests by using the same testing order random seed.

    opened by adamluzsi 1
  • support parameter passing with testcase.T#Defer

    support parameter passing with testcase.T#Defer

    What would it do

    This would allow to pass values from a scope with pass by value idiom.

    What is the Issue

    when Let memorize a pointer type and during the let block, it declares a T#Defer, the function body is only evaluated eventually. So if the struct content is mutated during the test, and then the deferred function tries to be executed with a value that might not be available at that point, it could cause issues.

    Example

    t.Defer(func(value string) {  }, `hello, world`)
    
    opened by adamluzsi 1
Releases(v0.108.0)
  • v0.108.0(Aug 12, 2022)

    • add the ability to manipulate observed time through the clock and timecop package
    • add experimental spec helpers to the httpspec
    • add support to simulate random io reads with random.Random
    • add the ability to pretty print any object, diff them in GNU side-by-side Diff style
    • support accessing a testcase.Var's Super value when you define it in a sub context
      • this allows you to wrap entities with Spies and Stubs to do various things, such as fault injection.
    • testcase.LetValue accepts more value as long they are safe to use due to pass-by-value.
    • support table tests with table test helper.

    Happy testing!

    Source code(tar.gz)
    Source code(zip)
  • v0.94.0(Jul 14, 2022)

    upgrade testcase to the latest

    • a new fault injection framework
      • allows injecting errors into a specific package/receiver/function's context.Err call
      • includes a fault injection middleware/round-tripper that allows propagating faults in distributed systems.
      • Added the ability to propagate injected faults in the round-tripper
      • allows the ability to inject errors through the HTTP API in a controlled manner
      • has a Global Enable Disable toggle to exclude all possibilities where faults injection is are not expected on an environment.
        • default is off
    defer faultinject.Enable()()
    
    ctx := context.Background()
    
    // all fault field is optional.
    // in case left as zero value,
    // it will match every caller context,
    // and returns on the first .Err() / .Value(faultinject.Fault{})
    ctx = faultinject.Inject(ctx, faultinject.CallerFault{
    	Package:  "targetpkg",
    	Receiver: "*myreceiver",
    	Function: "myfunction",
    }, errors.New("boom"))
    
    // from and after call stack reached: targetpkg.(*myreceiver).myfunction
    if err := ctx.Err(); err != nil {
    	fmt.Println(err) // in the position defined by the Fault, it will yield an error
    }
    
    • added support for a deterministic random generation as io.Reader with random.Random
    rnd := random.New(rand.NewSource(time.Now().Unix()))
    
    p := make([]byte, 42)
    n, err := rnd.Read(p)
    
    _, _ = n, err
    
    • added support for deterministic random error generation
    rnd := random.New(rand.NewSource(time.Now().Unix()))
    err := rnd.Error()
    _ = err
    
    • nil is an acceptable constant value for the testcase.LetValue
    • more examples in the pkg documentation
      • https://pkg.go.dev/github.com/adamluzsi/testcase#pkg-examples
      • https://pkg.go.dev/github.com/adamluzsi/testcase/random#pkg-examples
      • https://pkg.go.dev/github.com/adamluzsi/testcase/faultinject#pkg-examples
    • changed signature to accept receiver in the Before hook of a testcase.Var,
      • This allows the use of a global testcase.Var with a dynamic Before hook arrangement without the need to use the init() function.
    var v = testcase.Var[int]{
    	ID:   "myvar",
    	Init: func(t *testcase.T) int { return 42 },
    	Before: func(t *testcase.T, v testcase.Var[int]) {
    		t.Logf(`I'm from the Var.Before block, and the value: %#v`, v.Get(t))
    	},
    }
    
    • [EXPERIMENT] deprecate Around, AroundAll, After, AfterAll to trim down on the testcase's API
      • They can be expressed using a Before/BeforeAll with T.Defer or T.Cleanup
    • added support for sandboxing function blocks
      • This allows testing test helpers easily without letting your test exit with runtime.Goexit on testing.TB.FailNow
    var tb testing.TB = &testcase.StubTB{}
    outcome := testcase.Sandbox(func() {
    	// some test helper function calls fatal, which cause runtime.Goexit after marking the test failed.
    	tb.FailNow()
    })
    
    fmt.Println("The sandbox run has finished without an issue", outcome.OK)
    fmt.Println("runtime.Goexit was called:", outcome.Goexit)
    fmt.Println("panic value:", outcome.PanicValue)
    
    • added support for assert.NoError
    var tb testing.TB
    assert.NoError(tb, nil)                // passes
    assert.NoError(tb, errors.New("boom")) // fails
    
    • improved the usability of StubTB to make it easier to assert the log content
    stub := &testcase.StubTB{}
    stub.Log("hello", "world")
    fmt.Println(stub.Logs.String())
    
    • added commonly used charsets to make random string generation more convenient for a given charset
    rnd := random.New(rand.NewSource(time.Now().Unix()))
    rnd.StringNWithCharset(42, random.Charset())
    rnd.StringNWithCharset(42, random.CharsetASCII())
    rnd.StringNWithCharset(42, random.CharsetAlpha())
    rnd.StringNWithCharset(42, "ABC")
    
    • added alias for random.StringWithCharset to make it easier to use
    rnd := random.New(rand.NewSource(time.Now().Unix()))
    rnd.StringNC(42, random.Charset())
    
    • extend assert.ErrorIs to check equality even with wrapped values
    • move Eventually async test helper into the assert package
      • backward compatibility is ensured for awhile
    • add more safety guards to prevent variables without ID
    • added http.Handler middleware contract to httpspec
      • if you write an http.Handler-based middleware ensures that the minimum expectations are there.
    myHandlerWithFaultInjectionMiddleware := fihttp.Handler{
    	Next:        myHandler,
    	ServiceName: "our-service-name",
    	FaultsMapping: fihttp.FaultsMapping{
    		"mapped-fault-name": func(ctx context.Context) context.Context {
    			return faultinject.Inject(ctx, FaultTag{}, errors.New("boom"))
    		},
    	},
    }
    
    • many small improvements
    Source code(tar.gz)
    Source code(zip)
  • v0.80.0(Jun 7, 2022)

  • v0.78.0(May 25, 2022)

    Add testcase.T.SkipUntil that is equivalent to Log followed by SkipNow if the test is executing prior to the given deadline time. SkipUntil is useful when you need to skip something temporarily, but you don't trust your memory enough to return to it on your own.

    Source code(tar.gz)
    Source code(zip)
  • v0.77.0(May 18, 2022)

    assert.Asserter.Equal and other assertions that test equality will respect the .IsEqual method on the value. When it does a comparison and .IsEqual is defined, it will use that instead of deep equality checking.

    If a value has unexported values or a stateful field that requires a delicate way to compare, through this method, it becomes possible.

    example code for .IsEqual

    Source code(tar.gz)
    Source code(zip)
  • v0.76.2(May 18, 2022)

    httpspec is extended with a RoundTripperMiddleware contract.

    You can verify common scenarios with your round-tripper middleware using the shared spec. This shared contract should allow you to focus on the round-trippers business logic rather than the generic middleware behaviour.

    Source code(tar.gz)
    Source code(zip)
  • v0.74.0(May 2, 2022)

    Support for testing library creators

    The new StubTB allows testing test-helpers functions with a StubTB that behaves like the testing.TB.

    Expose http.Request and ResponseRecorder in httpspec

    This should allow reusing them as commonly used testcase.Var(s) in specs focusing on HTTP coverage.

    Source code(tar.gz)
    Source code(zip)
  • v0.72.0(Apr 11, 2022)

    • *testcase.T.Random.String from now on will return generic SQL and NoSQL injection strings as well.
    • signature change for fixtures.New to use Go's generics syntax.
    Source code(tar.gz)
    Source code(zip)
  • v0.71.0(Apr 9, 2022)

    The Big List of Naughty Strings is incorporated into the testcase's random generator. Unit tests that use these randomly generated string values will double as an enhanced fuzzing test.

    To run your unit tests with various naughty strings, use the -count testing flag. Each test execution by -count will run with a different naughty string.

    go test -count 1024 -run TestMyUnit
    
    Source code(tar.gz)
    Source code(zip)
  • v0.70.0(Mar 30, 2022)

    Exclude the time it takes to construct a dependency from the benchmark time.

    func Benchmark_myBench(b *testing.B) {
    	s := testcase.NewSpec(b)
    
    	v := testcase.Let(s, func(t *testcase.T) int {
    		// benchmark time is not tracked here
    		time.Sleep(time.Second / 2) 
    		return t.Random.Int()
    	})
    
    	s.Test(``, func(t *testcase.T) {
    		// benchmark time is tracked here 
    		time.Sleep(time.Second) 
    		_ = v.Get(t)
    	})
    }
    
    Source code(tar.gz)
    Source code(zip)
  • v0.69.0(Mar 30, 2022)

    Both testcase#Retry.Assert and testcase#T.Eventually use the same signature for convenience.

    waiter := testcase.Waiter{
    	WaitDuration: time.Millisecond,
    	WaitTimeout:  time.Second,
    }
    r := testcase.Retry{Strategy: waiter}
    r.Assert(t, func(it assert.It) { // <- assert.It is passed here instead of testing.TB
    	if rand.Intn(1) == 0 {
    		it.Fatal(`boom`)
    	}
    })
    
    Source code(tar.gz)
    Source code(zip)
  • v0.68.0(Mar 30, 2022)

    Assertions now support ErrorIs assertion where you can check both if the error is equal to an expected one, or part of the actual error's error chain. This leaves room for later refactoring like wrapping the error value to give better context to the returned error, without breaking a test.

    actualErr := errors.New("boom")
    assert.Must(tb).ErrorIs(errors.New("boom"), actualErr)                                  // passes for equality
    assert.Must(tb).ErrorIs(errors.New("boom"), fmt.Errorf("wrapped error: %w", actualErr)) // passes for wrapped errors
    
    Source code(tar.gz)
    Source code(zip)
  • v0.66.0(Mar 30, 2022)

    testcase's API is updated to remove the need for boilerplate when working with test bounded variables.

    previous version had to rely on empty interface casting:

    s := testcase.NewSpec(t)
    input := testcase.Var{ID: "input"}
    subject := func(t *testcase.T) string {
    	return MyFunc(input.Get(t).(string))
    }
    

    The latest release uses type safety through generics:

    s := testcase.NewSpec(t)
    input := testcase.Var[string]{ID: "input"}
    subject := func(t *testcase.T) string {
    	return MyFunc(input.Get(t))
    }
    

    Happy Coding/Specifying! :)

    Source code(tar.gz)
    Source code(zip)
  • v0.42.0(May 18, 2021)

    testcase.Race

    Race is a test helper that allows you to create a race situation easily. This is useful when you work on a component that requires thread-safety. By using the Race helper, you can write an example use of your component, and run the testing suite with go test -race. The race detector then should be able to notice issues with your implementation.

    testcase.T.Random

    T.Random is a random generator that uses the Spec seed. When a test fails with random input from Random generator, the failed test scenario can be recreated simply by providing the same TESTCASE_SEED as you can read from the console output of the failed test.

    testcase.Var Thread Safety

    testcase.Var.Get and testcase.Var.Set now thread-safe.

    Source code(tar.gz)
    Source code(zip)
  • v0.34.0(May 18, 2021)

    testcase.Contract & testcase.RunContract

    Contract meant to represent a Role Interface Contract. A role interface is a static code contract that expresses behavioral expectations as a set of method signatures. A role interface used by one or many consumers. These consumers often use implicit assumptions about how methods of the role interface behave. Using these assumptions makes it possible to simplify the consumer code. In testcase convention, instead of relying on implicit assumptions, the developer should create an explicit interface testing suite, in other words, a Contract. The code that supplies a role interface then able to import a role interface Contract, and confirm if the expected behavior is fulfilled by the implementation.

    further references:

    Source code(tar.gz)
    Source code(zip)
Owner
Adam Luzsi
In order to go fast, we need to go well
Adam Luzsi
A yaml data-driven testing format together with golang testing library

Specimen Yaml-based data-driven testing Specimen is a yaml data format for data-driven testing. This enforces separation between feature being tested

Design it, Run it 1 Nov 24, 2022
The test suite to demonstrate the chaos experiment behavior in different scenarios

Litmus-E2E The goal of litmus e2e is to provide the test suite to demonstrate the chaos experiment behavior in different scenarios. As the name sugges

Vedant Shrotria 0 Jul 7, 2022
Markdown based document-driven RESTful API testing.

silk Markdown based document-driven web API testing. Write nice looking Markdown documentation (like this), and then run it using the silk command Sim

Mat Ryer 934 Dec 18, 2022
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

Re 296 Dec 12, 2022
Learn Go with test-driven development

Learn Go with Tests Art by Denise Formats Gitbook EPUB or PDF Translations 中文 Português 日本語 한국어 Support me I am proud to offer this resource for free,

Chris James 19k Jan 1, 2023
Simple test driven approach in "GOLANG"

Testing in GOLANG Usage Only test go test -v Coverage go test -cover or go test -coverprofile=coverage.out go tool cover -html=coverage.out Benchmark

mahdi imani 0 Dec 5, 2021
Behaviour Driven Development tests generator for Golang

gherkingen It's a Behaviour Driven Development (BDD) tests generator for Golang. It accept a *.feature Cucumber/Gherkin file and generates a test boil

Maxim Krivchun 49 Dec 16, 2022
This repository includes consumer driven contract test for provider, unit test and counter api.

This repository includes consumer driven contract test for provider, unit test and counter api.

Ahmet Zümberoğlu 0 Feb 1, 2022
Fortio load testing library, command line tool, advanced echo server and web UI in go (golang). Allows to specify a set query-per-second load and record latency histograms and other useful stats.

Fortio Fortio (Φορτίο) started as, and is, Istio's load testing tool and now graduated to be its own project. Fortio is also used by, among others, Me

Fortio (Φορτίο) 2.8k Jan 2, 2023
:exclamation:Basic Assertion Library used along side native go testing, with building blocks for custom assertions

Package assert Package assert is a Basic Assertion library used along side native go testing Installation Use go get. go get github.com/go-playground/

Go Playgound 52 Jan 6, 2023