gintestutil

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Mar 16, 2024 License: MIT Imports: 11 Imported by: 0

README

🦁 Gin Test Utils

Go package GitHub GitHub go.mod Go version

Small utility functions for testing Gin-related code. Such as the creation of a gin context and wait groups with callbacks.

⬇️ Installation

go get github.com/ing-bank/gintestutil

📋 Usage

Context creation
package main

import (
	"net/http"
	"testing"
	"github.com/ing-bank/gintestutil"
)

type TestObject struct {
	Name string
}

func TestProductController_Post_CreatesProducts(t *testing.T) {
	// Arrange
	context, writer := gintestutil.PrepareRequest(t,
		gintestutil.WithJsonBody(t, TestObject{Name: "test"}),
		gintestutil.WithMethod(http.MethodPost),
		gintestutil.WithUrl("https://my-website.com"),
		gintestutil.WithHeaders(http.Header{"X-Example": []string{"A", "B"}}),
		gintestutil.WithUrlParams(map[string]any{"category": "barbecue"}),
	    gintestutil.WithQueryParams(map[string]any{"force": "true"}))

	// [...]
}
Response Assertions
package main

import (
	"github.com/stretchr/testify/assert"
	"net/http"
	"testing"
	"github.com/ing-bank/gintestutil"
)

type TestObject struct {
	Name string
}

func TestProductController_Index_ReturnsAllProducts(t *testing.T) {
	// Arrange
	context, writer := gintestutil.PrepareRequest(t)

	// [...]

	// Assert
	var actual []TestObject
	if gintestutil.Response(t, &actual, http.StatusOK, writer.Result()) {
		assert.Equal(t, []TestObject{}, actual)
	}
}
Hooks
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/ing-bank/gintestutil"
	"net/http"
	"net/http/httptest"
	"time"
	"testing"
)

func TestHelloController(t *testing.T) {
	// Arrange
	ginContext := gin.Default()

	// create expectation
	expectation := gintestutil.ExpectCalled(t, ginContext, "/hello-world")

	ginContext.GET("/hello-world", func(context *gin.Context) {
		context.Status(http.StatusOK)
	})

	// create webserver
	ts := httptest.NewServer(ginContext)

	// Send request to webserver path
	_, _ = http.Get(fmt.Sprintf("%s/hello-world", ts.URL))

	// Wait for expectation in bounded time
	if ok := gintestutil.EnsureCompletion(t, expectation); !ok {
		// do something
	}
}

🚀 Development

  1. Clone the repository
  2. Run make tools to install necessary tools
  3. Run make t to run unit tests
  4. Run make fmt to format code
  5. Run make lint to lint your code

You can run make to see a list of useful commands.

🔭 Future Plans

Nothing here yet!

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnsureCompletion

func EnsureCompletion(t *testing.T, wg *sync.WaitGroup, options ...EnsureOption) bool

EnsureCompletion ensures that the waitgroup completes within a specified duration or else fails

func ExpectCalled

func ExpectCalled(t TestingT, ctx *gin.Engine, path string, options ...ExpectOption) *sync.WaitGroup

ExpectCalled can be used on a gin endpoint to express an expectation that the endpoint will be called some time in the future. In combination with a test can wait for this expectation to be true or fail after some predetermined amount of time

Example (WithVarargs)

arguments can configure the expected amount of times and endpoint is called or re-use an existing expectation

t := new(testing.T)

ginContext := gin.Default()

// create expectation
expectation := ExpectCalled(t, ginContext, "/hello-world", TimesCalled(2))
expectation = ExpectCalled(t, ginContext, "/other-path", Expectation(expectation))

// create endpoints on ginContext
for _, endpoint := range []string{"/hello-world", "/other-path"} {
	ginContext.GET(endpoint, func(context *gin.Context) {
		context.Status(http.StatusOK)
	})
}

// create webserver
ts := httptest.NewServer(ginContext)

// Send request to webserver path
_, _ = http.Get(fmt.Sprintf("%s/hello-world", ts.URL))
_, _ = http.Get(fmt.Sprintf("%s/hello-world", ts.URL))
_, _ = http.Get(fmt.Sprintf("%s/other-path", ts.URL))

// Wait for expectation in bounded time
if ok := EnsureCompletion(t, expectation); !ok {
	// do something
}
Output:

Example (WithoutVarargs)

without arguments expect called assumes that the endpoint is only called once and creates a new expectation

t := new(testing.T)

ginContext := gin.Default()

// create expectation
expectation := ExpectCalled(t, ginContext, "/hello-world")

// create endpoints on ginContext
ginContext.GET("/hello-world", func(context *gin.Context) {
	context.Status(http.StatusOK)
})

// create webserver
ts := httptest.NewServer(ginContext)

// Send request to webserver path
_, _ = http.Get(fmt.Sprintf("%s/hello-world", ts.URL))

// Wait for expectation in bounded time
if ok := EnsureCompletion(t, expectation); !ok {
	// do something
}
Output:

func PrepareRequest

func PrepareRequest(t TestingT, options ...RequestOption) (*gin.Context, *httptest.ResponseRecorder)

PrepareRequest Formulate a request with optional properties. This returns a *gin.Context which can be used in controller unit-tests. Use the returned *httptest.ResponseRecorder to perform assertions on the response.

Example
// Arrange
t := new(mockT)
myController := &MyController{}

context, writer := PrepareRequest(t,
	WithMethod(http.MethodGet),
	WithUrl("https://ing.net"),
	WithJsonBody(t, MyObject{ID: 5}),
	WithUrlParams(map[string]any{"one": "two", "three": []string{"four", "five"}}),
	WithQueryParams(map[string]any{"id": map[string]any{"a": "b"}}),
)

// Act
myController.Post(context)

// Assert
assert.Equal(t, "...", writer.Body.String())
Output:

func Response

func Response(t TestingT, result any, code int, res *http.Response) bool

Response checks the status code and unmarshalls it to the given type. If you don't care about the response, Use nil. If the return code is 204 or 304, the response body is not converted.

Example
// Arrange
t := new(mockT)
expected := []MyObject{{ID: 5}}
myController := &MyController{}

context, writer := PrepareRequest(t)
defer writer.Result().Body.Close()

// Act
myController.Get(context)

var result []MyObject

// Assert
ok := Response(t, &result, http.StatusOK, writer.Result())

if assert.True(t, ok) {
	assert.Equal(t, expected[0].ID, result[0].ID)
}
Output:

Types

type EnsureOption

type EnsureOption func(*ensureConfig)

EnsureOption allows various options to be supplied to EnsureCompletion

func WithTimeout

func WithTimeout(timeout time.Duration) EnsureOption

WithTimeout is used to set a timeout for EnsureCompletion

type ExpectOption

type ExpectOption func(*calledConfig)

ExpectOption allows various options to be supplied to Expect* functions

func Expectation

func Expectation(expectation *sync.WaitGroup) ExpectOption

Expectation is used to have a global wait group to wait for when asserting multiple calls made

func TimesCalled

func TimesCalled(times int) ExpectOption

TimesCalled is used to expect an invocation an X amount of times

type RequestOption

type RequestOption func(*requestConfig)

RequestOption are functions used in PrepareRequest to configure a request using the Functional Option pattern.

func WithBody

func WithBody(data []byte) RequestOption

WithBody allows you to define a custom body for the request

func WithHeaders added in v0.0.1

func WithHeaders(headers http.Header) RequestOption

WithHeaders specifies the headers of the request

func WithJsonBody

func WithJsonBody(t TestingT, object any) RequestOption

WithJsonBody specifies the request body using json.Marshal, will report an error on marshal failure

func WithMethod

func WithMethod(method string) RequestOption

WithMethod specifies the method to use, defaults to Get

func WithQueryParams

func WithQueryParams(queryParams map[string]any) RequestOption

WithQueryParams adds query parameters to the request. The value can be either: - string - []string - fmt.Stringer (anything with a String() method) - map[string]any

func WithUrl

func WithUrl(reqUrl string) RequestOption

WithUrl specifies the url to use, defaults to https://example.com

func WithUrlParams

func WithUrlParams(parameters map[string]any) RequestOption

WithUrlParams adds url parameters to the request. The value can be either: - string - []string - fmt.Stringer (anything with a String() method)

type TestingT

type TestingT interface {
	Helper()
	Error(...any)
	Errorf(string, ...any)
}

TestingT is an interface representing testing.T in our tests, allows for verifying Errorf calls. It's perfectly compatible with the normal testing.T, but we use an interface for mocking.

Jump to

Keyboard shortcuts

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