Skip to content

A simple and extensible behavioural testing library for Go. You can use api test to simplify REST API, HTTP handler and e2e tests.

License

Notifications You must be signed in to change notification settings

nao1215/spectest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go Reference LinuxUnitTest MacUnitTest WindowsUnitTest UnitTestExampleCodes reviewdog Gosec Coverage

What is spectest?

A simple and extensible behavioral testing library. Supports mocking external http calls and renders sequence diagrams on completion. In behavioral tests the internal structure of the app is not known by the tests. Data is input to the system and the outputs are expected to meet certain conditions.

This project is forked from steinfletcher/apitest apitest was functionally complete. However, I wanted more features, so I decided to fork it to actively develop it further. I will mainly enhance document generation and integration with AWS. There are no plans for compatibility between apitest and spectest. Therefore, future development will include BREAKING CHANGES.

The spectest has its own unique use cases, Use Cases of spectest. Please refer to this document for more information.

Supported OS

  • Linux
  • Mac
  • Windows (Original apitest does not support Windows)

Installation

go get -u github.com/nao1215/spectest

Demo

animated gif

Examples

Framework and library integration examples

Example Comment
gin popular martini-like web framework
graphql using gqlgen.com to generate a graphql server
gorilla the gorilla web toolkit
iris (broken) iris web framework
echo High performance, extensible, minimalist Go web framework
fiber Express inspired web framework written in Go
httprouter High performance HTTP request router that scales well
mocks example mocking out external http calls
sequence diagrams generate sequence diagrams from tests
Ginkgo Ginkgo BDD test framework
plantuml wxample generating plantuml

Companion libraries (Side projects)

In the original apitest repository, side projects were managed in separate repositories. However, in spectest, these side projects are managed within the same repository. However, the difflib, which operates independently from spectest, and the malfunctioning aws package, are managed in separate repositories.

Library Comment
JSON Path JSON Path assertion addons
JOSN Schema JSON Schema assertion addons
CSS Selectors CSS selector assertion addons
PlantUML Export sequence diagrams as plantUML
DynamoDB (broken) Add DynamoDB interactions to sequence diagrams

Credits

This library was influenced by the following software packages:

  • YatSpec for creating sequence diagrams from tests
  • MockMVC and superagent for the concept and behavioral testing approach
  • Gock for the approach to mocking HTTP services in Go
  • Baloo for API design

Code snippets

JSON body matcher

func TestApi(t *testing.T) {
	spectest.New().
		Handler(handler).
		Get("/user/1234").
		Expect(t).
		Body(`{"id": "1234", "name": "Tate"}`).
		Status(http.StatusOK).
		End()
}

JSONPath

For asserting on parts of the response body JSONPath may be used. A separate module must be installed which provides these assertions - go get -u github.com/nao1215/spectest/jsonpath. This is packaged separately to keep this library dependency free.

Given the response is {"a": 12345, "b": [{"key": "c", "value": "result"}]}

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(jsonpath.Contains(`$.b[? @.key=="c"].value`, "result")).
		End()
}

and jsonpath.Equals checks for value equality

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(jsonpath.Equal(`$.a`, float64(12345))).
		End()
}

Custom assert functions

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		Expect(t).
		Assert(func(res *http.Response, req *http.Request) error {
			assert.Equal(t, http.StatusOK, res.StatusCode)
			return nil
		}).
		End()
}

Assert cookies

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Patch("/hello").
		Expect(t).
		Status(http.StatusOK).
		Cookies(spectest.Cookie("ABC").Value("12345")).
		CookiePresent("Session-Token").
		CookieNotPresent("XXX").
		Cookies(
			spectest.Cookie("ABC").Value("12345"),
			spectest.Cookie("DEF").Value("67890"),
		).
		End()
}

Assert headers

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Headers(map[string]string{"ABC": "12345"}).
		End()
}

Mocking external http calls

var getUser = spectest.NewMock().
	Get("/user/12345").
	RespondWith().
	Body(`{"name": "jon", "id": "1234"}`).
	Status(http.StatusOK).
	End()

var getPreferences = spectest.NewMock().
	Get("/preferences/12345").
	RespondWith().
	Body(`{"is_contactable": true}`).
	Status(http.StatusOK).
	End()

func TestApi(t *testing.T) {
	spectest.New().
		Mocks(getUser, getPreferences).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Body(`{"name": "jon", "id": "1234"}`).
		End()
}

Generating sequence diagrams from tests

func TestApi(t *testing.T) {
	spectest.New().
		Report(spectest.SequenceDiagram()).
		Mocks(getUser, getPreferences).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		Body(`{"name": "jon", "id": "1234"}`).
		End()
}

It is possible to override the default storage location by passing the formatter instance Report(spectest.NewSequenceDiagramFormatter(".sequence-diagrams")). If you want to change the report file name , you use CustomReportName("file name is here") . By default, the hash value becomes the report file name.

You can bring your own formatter too if you want to produce custom output. By default a sequence diagram is rendered on a html page.

The spectest checks the Content Type of the response. If it's an image-related MIME type, the image will be displayed in the report. In the apitest, binary data was being displayed.

DiagramSample

One feature that does not exist in the apitest fork is the ability to output reports in markdown format. The below code snippet is an example of how to output a markdown report.

func TestApi(t *testing.T) {
	spectest.New().
		CustomReportName("markdow_report").
		Report(spectest.SequenceReport(spectest.ReportFormatterConfig{
			Path: "doc",
			Kind: spectest.ReportKindMarkdown,
		})).
		Handler(handler).
		Get("/image").
		Expect(t).
		Body(string(body)).
		Header("Content-Type", "image/png").
		Header("Content-Length", fmt.Sprint(imageInfo.Size())).
		Status(http.StatusOK).
		End()
}

MarkdownReportSample

Debugging http requests and responses generated by api test and any mocks

func TestApi(t *testing.T) {
	spectest.New().
		Debug().
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide basic auth in the request

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		BasicAuth("username", "password").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Pass a custom context to the request

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		WithContext(context.TODO()).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide cookies in the request

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		Cookies(spectest.Cookie("ABC").Value("12345")).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide headers in the request

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Delete("/hello").
		Headers(map[string]string{"My-Header": "12345"}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide query parameters in the request

Query, QueryParams and QueryCollection can all be used in combination

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		QueryParams(map[string]string{"a": "1", "b": "2"}).
		Query("c", "d").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Providing {"a": {"b", "c", "d"} results in parameters encoded as a=b&a=c&a=d. QueryCollection can be used in combination with Query

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Get("/hello").
		QueryCollection(map[string][]string{"a": {"b", "c", "d"}}).
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide a url encoded form body in the request

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Post("/hello").
		FormData("a", "1").
		FormData("b", "2").
		FormData("b", "3").
		FormData("c", "4", "5", "6").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Provide a multipart/form-data

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Post("/hello").
		MultipartFormData("a", "1", "2").
		MultipartFile("file", "path/to/some.file1", "path/to/some.file2").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Capture the request and response data

func TestApi(t *testing.T) {
	spectest.New().
		Observe(func(res *http.Response, req *http.Request, specTest *spectest.SpecTest) {
			// do something with res and req
		}).
		Handler(handler).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Intercept the request

This is useful for mutating the request before it is sent to the system under test.

func TestApi(t *testing.T) {
	spectest.Handler(handler).
		Intercept(func(req *http.Request) {
			req.URL.RawQuery = "a[]=xxx&a[]=yyy"
		}).
		Get("/hello").
		Expect(t).
		Status(http.StatusOK).
		End()
}

Contributing

View the contributing guide.