fixify

package module
v0.0.0-...-9c0beee Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2024 License: MIT Imports: 3 Imported by: 0

README

fixify

Go Reference test codecov

fixify is a Go library that helps you to write test fixtures in a declarative way.

func TestRun(t *testing.T) {
	// specify how to connect models in a declarative way.
	f := fixify.New(t,
		Company().With(
			Department("finance").With(
				Employee(),
				Employee(),
			),
			Department("sales").With(
				Employee(),
				Employee(),
				Employee(),
			),
		),
	)
	// Apply resolves the dependencies and applies a visitor to each model.
	f.Apply(setter)
	// finally, run the test!
}

// Department is a fixture for model.Department.
func Department(name string) *fixify.Model[model.Department] {
	d := &model.Department{
		Name: name,
	}
	return fixify.NewModel(d,
		// specify how to connect a department to a company.
		fixify.ConnectorFunc(func(_ testing.TB, department *model.Department, company *model.Company) {
			department.CompanyID = company.ID
		}),
	)
}

For more examples, please refer to the godoc.

References

Documentation

Overview

Example
package main

import (
	"fmt"
	"slices"
	"strings"
	"testing"

	"github.com/qawatake/fixify"
	"github.com/qawatake/fixify/internal/example/model"
)

func main() {
	// t is passed from the test function.
	t := &testing.T{}
	// specify how to connect models in a declarative way.
	f := fixify.New(t,
		Company().With(
			Department("finance").With(
				Employee(),
			),
			Department("sales"),
		),
	)
	// Apply applies visitor function to each model and then call the connector functions.
	f.Apply(func(v any) error {
		switch v := v.(type) {
		case *model.Company:
			v.ID = 1
		case *model.Department:
			if v.Name == "finance" {
				v.ID = 2
			} else {
				v.ID = 3
			}
		case *model.Employee:
			v.ID = 4
		}
		return nil
	})
	allModels := f.All()
	for _, company := range filter[*model.Company](allModels) {
		fmt.Printf("CompanyID: %d\n", company.ID)
	}
	for _, department := range sortDepartments(filter[*model.Department](allModels)) {
		fmt.Printf("DepartmentID: %d Name: %s CompanyID: %d\n", department.ID, department.Name, department.CompanyID)
	}
	for _, employee := range filter[*model.Employee](allModels) {
		fmt.Printf("EmployeeID: %d DepartmentID: %d\n", employee.ID, employee.DepartmentID)
	}
}

// Company represents a fixture for the company model.
func Company() *fixify.Model[model.Company] {
	// Company is the root model, so it does not need a connector function.
	return fixify.NewModel(new(model.Company))
}

// Department represents a fixture for the department model.
func Department(name string) *fixify.Model[model.Department] {
	d := &model.Department{
		Name: name,
	}
	return fixify.NewModel(d,
		// specify how to connect a department to a company.
		fixify.ConnectorFunc(func(_ testing.TB, department *model.Department, company *model.Company) {
			department.CompanyID = company.ID
		}),
	)
}

// Employee represents a fixture for the employee model.
func Employee() *fixify.Model[model.Employee] {
	return fixify.NewModel(new(model.Employee),
		// specify how to connect an employee to a department.
		fixify.ConnectorFunc(func(_ testing.TB, employee *model.Employee, department *model.Department) {
			employee.DepartmentID = department.ID
		}),
	)
}

func filter[T any](models []any) []T {
	filtered := make([]T, 0, len(models))
	for _, v := range models {
		if v, ok := v.(T); ok {
			filtered = append(filtered, v)
		}
	}
	return filtered
}

func sortDepartments(departments []*model.Department) []*model.Department {
	slices.SortFunc(departments, func(a, b *model.Department) int {
		return strings.Compare(a.Name, b.Name)
	})
	return departments
}
Output:

CompanyID: 1
DepartmentID: 2 Name: finance CompanyID: 1
DepartmentID: 3 Name: sales CompanyID: 1
EmployeeID: 4 DepartmentID: 2

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Connecter

type Connecter[T any] interface {
	// contains filtered or unexported methods
}

Connector is an interface that incorporates the connector functions of the form func(t testing.TB, childModel *U, parentModel *V). It is used to establish connections between different model types. Use ConnectorFunc to get one.

func ConnectorFunc

func ConnectorFunc[U, V any](f func(t testing.TB, childModel *U, parentModel *V)) Connecter[U]

ConnectorFunc translates a function of the form func(t testing.TB, childModel *U, parentModel *V) into Connecter[U].

func ConnectorFuncWithLabel

func ConnectorFuncWithLabel[U, V any, L comparable](label L, f func(t testing.TB, childModel *U, parentModel *V)) Connecter[U]

ConnectorFuncWithLabel translates a function of the form func(t testing.TB, childModel *U, parentModel *V) with a label into Connecter[U]. With different labels, you can connect the same parent model in different ways. See an example in Model.WithParentAs.

type Fixture

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

Fixture collects models and resolves their dependencies.

func New

func New(tb testing.TB, fixtures ...IModel) *Fixture

func (*Fixture) All

func (f *Fixture) All() []any

All returns all models in the fixture.

Example
// t is passed from the test function.
t := &testing.T{}
f := fixify.New(t,
	Company().With(
		Department("finance").With(
			Employee(),
		),
		Department("sales"),
	),
)
// a model is either *model.Company, *model.Department, or *model.Employee.
models := f.All()

fmt.Println("Number of companies:", len(filter[*model.Company](models)))
fmt.Println("Number of departments:", len(filter[*model.Department](models)))
fmt.Println("Number of employees:", len(filter[*model.Employee](models)))
Output:

Number of companies: 1
Number of departments: 2
Number of employees: 1

func (*Fixture) Apply

func (f *Fixture) Apply(visit func(model any) error)

Apply applies visit and call connector functions in the topological order of the models.

type IModel

type IModel interface {
	// contains filtered or unexported methods
}

IModel represents a set of models that can be connected to each other.

type Model

type Model[T any] struct {
	// contains filtered or unexported fields
}

Model is a wrapper model with ability to connect to other models. It implements IModel.

func NewModel

func NewModel[T any](model *T, connectorFuncs ...Connecter[T]) *Model[T]

NewModel is a constructor of Model.

Example
package main

import (
	"testing"

	"github.com/qawatake/fixify"
	"github.com/qawatake/fixify/internal/example/model"
)

func main() {
	// t is passed from the test function.
	t := &testing.T{}
	fixify.New(t,
		Book(),
	)
}

// Book represents a fixture for the book model.
func Book() *fixify.Model[model.Book] {
	return fixify.NewModel(
		&model.Book{},
		fixify.ConnectorFunc(func(_ testing.TB, book *model.Book, library *model.Library) {
			book.LibraryID = library.ID
		}),
	)
}

func (*Model[T]) Bind

func (m *Model[T]) Bind(b **Model[T]) *Model[T]

Bind sets the pointer to the model. It is useful when you want to connect models to multiple parents.

Example
package main

import (
	"testing"

	"github.com/qawatake/fixify"
	"github.com/qawatake/fixify/internal/example/model"
)

func main() {
	// t is passed from the test function.
	t := &testing.T{}
	var enrollment *fixify.Model[model.Enrollment]
	fixify.New(t,
		Student().With(
			Enrollment().Bind(&enrollment),
		),
		Classroom().With(
			enrollment,
		),
	)
}

func Student() *fixify.Model[model.Student] {
	return fixify.NewModel(new(model.Student))
}

func Classroom() *fixify.Model[model.Classroom] {
	return fixify.NewModel(new(model.Classroom))
}

func Enrollment() *fixify.Model[model.Enrollment] {
	return fixify.NewModel(new(model.Enrollment),
		fixify.ConnectorFunc(func(_ testing.TB, enrollment *model.Enrollment, student *model.Student) {
			enrollment.StudentID = student.ID
		}),
		fixify.ConnectorFunc(func(_ testing.TB, enrollment *model.Enrollment, classroom *model.Classroom) {
			enrollment.ClassroomID = classroom.ID
		}),
	)
}

func (*Model[T]) Value

func (m *Model[T]) Value() *T

Value returns the underlying model.

func (*Model[T]) With

func (m *Model[T]) With(children ...IModel) *Model[T]

With registers children models.

Example
// Company, Department, and Employee are fixtures for the company, department, and employee models.
Company().With(
	Department("finance").With(
		Employee(),
		Employee(),
	),
	Department("sales").With(
		Employee(),
		Employee(),
		Employee(),
	),
)

func (*Model[T]) WithParent

func (m *Model[T]) WithParent(parent IModel) *Model[T]

WithParent registers a parent model.

func (*Model[T]) WithParentAs

func (m *Model[T]) WithParentAs(label any, parent IModel) *Model[T]

WithParentAs registers a parent model with a label.

Example
package main

import (
	"testing"

	"github.com/qawatake/fixify"
	"github.com/qawatake/fixify/internal/example/model"
)

func main() {
	// t is passed from the test function.
	t := &testing.T{}
	fixify.New(t,
		Follow().
			WithParentAs("follower", User("bob")).
			WithParentAs("followee", User("alice")),
	)
}

func User(name string) *fixify.Model[model.User] {
	return fixify.NewModel(&model.User{
		Name: name,
	})
}

func Follow() *fixify.Model[model.Follow] {
	return fixify.NewModel(new(model.Follow),
		fixify.ConnectorFuncWithLabel("follower", func(_ testing.TB, follow *model.Follow, follower *model.User) {
			follow.FollowerID = follower.ID
		}),
		fixify.ConnectorFuncWithLabel("followee", func(_ testing.TB, follow *model.Follow, followee *model.User) {
			follow.FolloweeID = followee.ID
		}),
	)
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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