fixture

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 10, 2023 License: Apache-2.0 Imports: 9 Imported by: 0

README

fixture

Re-usable test setups und teardowns for go(lang) testing tests.

CI Status Go Report Card Package Doc Releases

fixture implements a micro-framework ontop of the standard library's testing package that allow writing of reusable test setup and teardown code.

Installation

This module uses golang modules and can be installed with

go get github.com/halimath/fixture@main

Usage

fixture defines a very simple (and assumingly well-known) lifecycle for code to execute before, inbetween and after tests. A fixture may hook into this lifecycle to setup or teardown resources needed by the tests. As multiple tests may share some amount of these resources fixture provides a simple test suite functionality that plays well with the resource initialization.

The lifecycle is shown in the following picture:

lifecycle

A fixture (in terms of this package) is any go value. A fixture may satisfy a couple of additional interfaces to execute code at the given lifecycle phases. The interfaces are named after the lifecycle phases. Each interface contains a single method (named after the interface) that receives the *testing.T and returns an error which will abort the test (calling t.Fatal).

Using a fixture

Using a fixture is done using the With function, which starts a new test suite. Calling Run registers a test to run using this fixture.

With(t, new(myFixture)).
	Run("test 1", func(t *testing.T, f *myFixture) {
		// Test code
	}).
	Run("test 2", func(t *testing.T, f *myFixture) {
		// Test code
	})

Implementing a fixture

To implement a fixture simply create a type to hold all the values your fixture will provide. You can also add receiver functions to ease interaction with the fixture. Then, implement the desired hook interfaces.

Typically, a fixture implements the hook methods via a pointer receiver. This allows using just new to create a fixture. Use either BeforeAll or BeforeEach to initialize the code.

The following example uses a fixture to spawn a httptest.Server with a simple handler (in a real world the handle would have been some real production code). It provides a sendRequest method to send a simple request, handle errors by failing the test and returns the http.Response.

The TestExample executes two tests both using the same running server.

// A simple test fixture holding a httptest.Server.
type httpServerFixture struct {
	srv *httptest.Server
}

// BeforeAll hooks into the fixture lifecycle and creates and starts the
// httptest.Server before the first test is executed.
func (f *httpServerFixture) BeforeAll(t *testing.T) error {
	f.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Tracing-Id", "1")
		w.WriteHeader(http.StatusNoContent)
	}))

	return nil
}

// AfterAll hooks into the fixture lifecycle and disposes the httptest.Server
// after the last test has been executed.
func (f *httpServerFixture) AfterAll(t *testing.T) error {
	f.srv.Close()
	return nil
}

// sendRequest is a convenience function making it easier to read the test code.
func (f *httpServerFixture) sendRequest(t *testing.T) *http.Response {
	r, err := http.Get(f.srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	return r
}

func TestExample(t *testing.T) {
	fixture.With(t, new(httpServerFixture)).
		Run("http status code", func(t *testing.T, f *httpServerFixture) {
			got := f.sendRequest(t).StatusCode
			if got != http.StatusNoContent {
				t.Errorf("expected %d but got %d", http.StatusNoContent, got)
			}
		}).
		Run("tracing header", func(t *testing.T, f *httpServerFixture) {
			resp := f.sendRequest(t)
			got := resp.Header.Get("X-Tracing-Id")
			if got != "1" {
				t.Errorf("expected %q but got %q", "1", got)
			}
		})
}

Fixtures already provided by fixture

fixture contains some ready to use generic fixtures. All these fixtures have dependencies only to the standard library and cause no external module to be required.

TempDir

Creating and removing a temporary directory for filesystem related tests is easy with the TempDirFixture and the TempDir function.

With(t, TempDir("someprefix")).
	Run("create file", func(t *testing.T, d *TempDirFixture) {
		f, err := os.Create(d.Join("test"))
		if err != nil {
			t.Fatal(err)
		}
		defer f.Close()
	}).
	Run("expect file", func(t *testing.T, d *TempDirFixture) {
		_, err := os.Stat(d.Join("test"))
		if err != nil {
			t.Error(err)
		}
	})
HTTPServerFixture

The HTTPServerFixture creates a HTTP server using httptest.NewServer which will be started on BeforeAll and closed on AfterAll. The server uses a http.ServerMux as its handler and handler functions can be registered at any stage. The server uses HTTP/2 but no TLS; both can be changed easily.

f := new(HTTPServerFixture)

f.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
})

With(t, f).
	Run("/", func(t *testing.T, f *HTTPServerFixture) {
		res, err := http.Get(f.URL())
		if err != nil {
			t.Fatal(err)
		}

		if res.StatusCode != http.StatusOK {
			t.Errorf("expected 200 but got %d", res.StatusCode)
		}
	})

License

Copyright 2022 Alexander Metzner.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

Package fixture provides a micro-framework on top of the testing package that provides test setup and teardown handling in a reusable way. The package defines a (well-known) lifecycle to execute, which is in detail documented in the repo's README file.

A fixture is any go value. To hook into the lifecycle a fixture must satisfy any of the single-method interfaces defined.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AfterAll

type AfterAll interface {
	Fixture
	AfterAll(t *testing.T) error
}

AfterAll is an extension hook interface that defines the AfterAll hook.

type AfterEach

type AfterEach interface {
	AfterEach(t *testing.T) error
}

AfterEach is an extension hook interface that defines the AfterEach hook.

type BeforeAll

type BeforeAll interface {
	Fixture
	BeforeAll(t *testing.T) error
}

BeforeAll is an extension hook interface that defines the BeforeAll hook.

type BeforeEach

type BeforeEach interface {
	BeforeEach(t *testing.T) error
}

BeforeEach is an extension hook interface that defines the BeforeEach hook.

type Fixture

type Fixture interface{}

Fixture defines a "label" interface for fixtures providing values for tests. This interface defines no methods and is completely equivalent to any. It just serves the purpose of making the generic type annotations easier to reason about.

type HTTPServerFixture

type HTTPServerFixture struct {
	UseTLS       bool
	DisableHTTP2 bool
	// contains filtered or unexported fields
}

HTTPServerFixture is a fixture that provides a httptest.Server for testing. The server will be started on BeforeAll and closed on AfterAll. The server is started with HTTP2 enabled but without TLS by default. Both can be changed by setting the boolean flags on the fixture.

func (*HTTPServerFixture) AfterAll

func (f *HTTPServerFixture) AfterAll(t *testing.T) error

func (*HTTPServerFixture) BeforeAll

func (f *HTTPServerFixture) BeforeAll(t *testing.T) error

func (*HTTPServerFixture) Handle

func (f *HTTPServerFixture) Handle(pattern string, handler http.Handler)

func (*HTTPServerFixture) HandleFunc

func (f *HTTPServerFixture) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))

func (*HTTPServerFixture) URL

func (f *HTTPServerFixture) URL(pathElements ...string) string

URL returns the url used to connect to the test server formed by using the server's base url (which contains the randomly chosen port as well as the respective protocol) and all elements from pathElements joined with a /.

type MultiFixture

type MultiFixture []Fixture

MultiFixture combines multiple fixtures into a single one to use with With. It implements every hook interface and delegates all hooks to each fixture. The order of delegation is defined by the hooks type:

  • Before-hooks are execute first-to-last order
  • After-hooks are executed in last-to-first order

func (MultiFixture) AfterAll

func (f MultiFixture) AfterAll(t *testing.T) error

func (MultiFixture) AfterEach

func (f MultiFixture) AfterEach(t *testing.T) error

func (MultiFixture) BeforeAll

func (f MultiFixture) BeforeAll(t *testing.T) error

func (MultiFixture) BeforeEach

func (f MultiFixture) BeforeEach(t *testing.T) error

type Suite

type Suite[F Fixture] interface {
	Run(string, TestFunc[F]) Suite[F]
}

Suite defines a suite of tests using the same fixture.

func With

func With[F any](t *testing.T, fixture F) Suite[F]

With is used to define a new Suite based on fixture.

type TempDirFixture

type TempDirFixture struct {
	// contains filtered or unexported fields
}

func TempDir

func TempDir(name string) *TempDirFixture

func (*TempDirFixture) AfterAll

func (f *TempDirFixture) AfterAll(t *testing.T) error

func (*TempDirFixture) BeforeAll

func (f *TempDirFixture) BeforeAll(t *testing.T) error

func (*TempDirFixture) Join

func (f *TempDirFixture) Join(parts ...string) string

func (*TempDirFixture) Name

func (f *TempDirFixture) Name() string

func (*TempDirFixture) Path

func (f *TempDirFixture) Path() string

func (*TempDirFixture) String

func (f *TempDirFixture) String() string

type TestFunc

type TestFunc[F Fixture] func(*testing.T, F)

TestFunc defines the type for test functions running a test on behalf of a Fixture.

type TupleFixture

type TupleFixture[A, B Fixture] struct {
	One A
	Two B
	// contains filtered or unexported fields
}

TupleFixture is a convenience type used to combine exactly two fixtures into a single one to use with tests. It uses MultiFixture under the hood but wraps it inside a struct that exposes the fixtures statically typed.

func Tuple

func Tuple[A, B Fixture](a A, b B) *TupleFixture[A, B]

func (*TupleFixture[A, B]) AfterAll

func (f *TupleFixture[A, B]) AfterAll(t *testing.T) error

func (*TupleFixture[A, B]) AfterEach

func (f *TupleFixture[A, B]) AfterEach(t *testing.T) error

func (*TupleFixture[A, B]) BeforeAll

func (f *TupleFixture[A, B]) BeforeAll(t *testing.T) error

func (*TupleFixture[A, B]) BeforeEach

func (f *TupleFixture[A, B]) BeforeEach(t *testing.T) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL