CUTE — create your tests easily
HTTP and REST API testing for Go using Allure reports.
Features
- Expressive and intuitive syntax.
- Built-in JSON support.
- Custom asserts.
- One step to BDD.
- Allure reports.
Head of contents
Workflow
- Create a request and write assets.
- Run tests.
- Check Allure reports.
Installation
go get -u github.com/ozontech/cute
Requirements
- Go 1.17+
Demo
Run example.
To view detailed test reports, install Allure framework. It's optional.
Learn more about Allure reports
Run Allure.
allure serve ./examples/allure-results
Test examples
See Examples directory for featured examples.
Single-step test
Allows implementing single-request tests. See full example in the Examples directory.
To view an Allure report, use testing.T or provider.T from allure-go.
import ( "context" "net/http" "path" "testing" "time" "github.com/ozontech/cute" "github.com/ozontech/cute/asserts/json" ) func TestExample(t *testing.T) { cute.NewTestBuilder(). Title("Title"). Description("some_description"). Create(). RequestBuilder( cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), cute.WithMethod(http.MethodGet), ). ExpectExecuteTimeout(10*time.Second). ExpectStatus(http.StatusOK). AssertBody( json.Equal("$[0].email", "Eliseo@gardner.biz"), json.Present("$[1].name"), ). ExecuteTest(context.Background(), t) }
Multi-step test
Allows implementing several requests within one test.
import ( "context" "fmt" "net/http" "testing" "github.com/ozontech/cute" ) func Test_TwoSteps(t *testing.T) { responseCode := 0 // First step cute.NewTestBuilder(). Title("Test with two requests and parse body."). Tag("two_steps"). Create(). RequestBuilder( cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), cute.WithMethod(http.MethodGet), ). ExpectStatus(http.StatusOK). NextTest(). // Execute after first step and parse response code AfterTestExecute(func(response *http.Response, errors []error) error { responseCode = response.StatusCode return nil }). // Second step Create(). RequestBuilder( cute.WithURI("https://jsonplaceholder.typicode.com/posts/2/comments"), cute.WithMethod(http.MethodDelete), ). ExecuteTest(context.Background(), t) fmt.Println("Response code from first request", responseCode) }
See full in the Examples directory.
Suite
Suite provides a structure for describing tests by organizing them into test suites. It's helpful if you have a large number of different tests and find it difficult to browse through them without using additional layer nesting levels of test calls.
Learn more about suite with Allure reports
- Declare a structure with
suite.Suiteand*cute.HTTPTestMaker.
import ( "github.com/ozontech/cute" "github.com/ozontech/allure-go/pkg/framework/provider" "github.com/ozontech/allure-go/pkg/framework/suite" ) type ExampleSuite struct { suite.Suite host *url.URL testMaker *cute.HTTPTestMaker } func (i *ExampleSuite) BeforeAll(t provider.T) { // Prepare http test builder i.testMaker = cute.NewHTTPTestMaker() // Preparing host host, err := url.Parse("https://jsonplaceholder.typicode.com/") if err != nil { t.Fatalf("could not parse url, error %w", err) } i.host = host }
- Declare a test.
import ( "github.com/ozontech/allure-go/pkg/framework/suite" ) func TestExampleTest(t *testing.T) { suite.RunSuite(t, new(ExampleSuite)) }
- Describe tests.
import ( "github.com/ozontech/cute" "github.com/ozontech/cute/asserts/headers" "github.com/ozontech/cute/asserts/json" ) func (i *ExampleSuite) TestExample_OneStep(t provider.T) { var ( testBuilder = i.testMaker.NewTestBuilder() ) u, _ := url.Parse(i.host.String()) u.Path = path.Join(u.Path, "/posts/1/comments") testBuilder. Title("TestExample_OneStep"). Tags("one_step", "some_local_tag", "json"). Create(). StepName("Example GET json request"). RequestBuilder( cute.WithHeaders(map[string][]string{ "some_header": []string{"something"}, "some_array_header": []string{"1", "2", "3", "some_thing"}, }), cute.WithURL(u), cute.WithMethod(http.MethodGet), ). ExpectExecuteTimeout(10*time.Second). ExpectJSONSchemaFile("file://./resources/example_valid_request.json"). ExpectStatus(http.StatusOK). AssertBody( json.Equal("$[0].email", "Eliseo@gardner.biz"), json.Present("$[1].name"), json.NotPresent("$[1].some_not_present"), json.GreaterThan("$", 3), json.Length("$", 5), json.LessThan("$", 100), json.NotEqual("$[3].name", "kekekekeke"), ). OptionalAssertBody( json.GreaterThan("$", 3), json.Length("$", 5), json.LessThan("$", 100), ). AssertHeaders( headers.Present("Content-Type"), ). ExecuteTest(context.Background(), t) }
See full example in the Examples directory.
Table tests
You can create a table test in 2 ways. They'll have the same Allure reports.
Builder table tests
import ( "context" "fmt" "net/http" "testing" "github.com/ozontech/cute" ) func Test_Table_Array(t *testing.T) { tests := []*cute.Test{ { Name: "test_1", Middleware: nil, Request: &cute.Request{ Builders: []cute.RequestBuilder{ cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), cute.WithMethod(http.MethodPost), }, }, Expect: &cute.Expect{ Code: 200, }, }, { Name: "test_2", Middleware: nil, Request: &cute.Request{ Builders: []cute.RequestBuilder{ cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), cute.WithMethod(http.MethodGet), }, }, Expect: &cute.Expect{ Code: 200, AssertBody: []cute.AssertBody{ json.Equal("$[0].email", "Eliseo@gardner.biz"), json.Present("$[1].name"), func(body []byte) error { return errors.NewAssertError("example error", "example message", nil, nil) }, }, }, }, } cute.NewTestBuilder(). Title("Example table test"). Tag("table_test"). Description("Execute array tests"). CreateTableTest(). PutTests(tests...). ExecuteTest(context.Background(), t) }
Array tests
func Test_Execute_Array(t *testing.T) { tests := []*cute.Test{ { Name: "test_1", Middleware: nil, Request: &cute.Request{ Builders: []cute.RequestBuilder{ cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), cute.WithMethod(http.MethodPost), }, }, Expect: &cute.Expect{ Code: 200, }, }, { Name: "test_2", Middleware: nil, Request: &cute.Request{ Builders: []cute.RequestBuilder{ cute.WithURI("https://jsonplaceholder.typicode.com/posts/1/comments"), cute.WithMethod(http.MethodGet), }, }, Expect: &cute.Expect{ Code: 200, AssertBody: []cute.AssertBody{ json.Equal("$[0].email", "Eliseo@gardner.biz"), json.Present("$[1].name"), func(body []byte) error { return errors.NewAssertError("example error", "example message", nil, nil) }, }, }, }, } for _, test := range tests { test.Execute(context.Background(), t) } }
See full example in the Examples directory.
Asserts
You can create your own asserts or use ready-made from the package asserts.
Ready-made asserts
JSON asserts
Equalis a function to assert that a JSONPath expression matches the given value.NotEqualis a function to check that a JSONPath expression value isn't equal to the given value.Lengthis a function to assert that value is the expected length.GreaterThanis a function to assert that value is greater than the given length.GreaterOrEqualThanis a function to assert that value is greater or equal to the given length.LessThanis a function to assert that value is less than the given length.LessOrEqualThanis a function to assert that value is less or equal to the given length.
Presentis a function to assert that value is present. Value can be 0 or null.NotEmptyis a function to assert that value is present and not empty. Value can't be 0 or null.NotPresentis a function to assert that value isn't present.Diffis a function to compare two JSONs.Containsis a function to assert that a JSONPath expression extracts a value in an array.EqualJSONis a function to check that a JSON path expression value is equal to given JSON.NotEqualJSONis a function to check that a JSONPath expression value isn't equal to given JSON.GetValueFromJSONis a function for getting a value from a JSON.
Learn more about asserts implementation
Headers asserts
Presentis a function to assert that header is present.NotPresentis a function to assert that header isn't present.
Learn more about asserts implementation
JSON schema validations
You can validate a JSON schema in 3 ways. Choose a way depending on JSON schema location.
ExpectJSONSchemaString(string)is a function for validating a JSON schema from a string.ExpectJSONSchemaByte([]byte)is a function for validating a JSON schema from an array of bytes.ExpectJSONSchemaFile(string)is a function for validating a JSON schema from a file or remote resource.
Custom asserts
You can implement 3 type of asserts:
Base
Types for creating custom assertions.
type AssertBody func(body []byte) error type AssertHeaders func(headers http.Header) error type AssertResponse func(response *http.Response) error
Example:
func customAssertBody() cute.AssertBody { return func(bytes []byte) error { if len(bytes) == 0 { return errors.New("response body is empty") } return nil } }
T
Used for creating custom asserts via Allure Actions and testing.TB. You can:
- log information to Allure,
- log error on Allure yourself,
- return an error.
type AssertBodyT func(t cute.T, body []byte) error type AssertHeadersT func(t cute.T, headers http.Header) error type AssertResponseT func(t cute.T, response *http.Response) error
Example with T:
func customAssertBodyT() cute.AssertBodyT { return func(t cute.T, bytes []byte) error { require.GreaterOrEqual(t, len(bytes), 100) return nil } }
Example with creating steps:
func customAssertBodySuite() cute.AssertBodyT { return func(t cute.T, bytes []byte) error { step := allure.NewSimpleStep("Custom assert step") defer func() { t.Step(step) }() if len(bytes) == 0 { step.Status = allure.Failed step.Attachment(allure.NewAttachment("Error", allure.Text, []byte("response body is empty"))) return nil } return nil } }
Assert errors
You can use errors.NewAssertError method from errors package.
Example:
import ( "github.com/ozontech/cute" "github.com/ozontech/cute/errors" ) func customAssertBodyWithCustomError() cute.AssertBody { return func(bytes []byte) error { if len(bytes) == 0 { return errors.NewAssertError("customAssertBodyWithCustomError", "body must be not empty", "len is 0", "len more 0") } return nil } }
To create a pretty-error in your custom assert, implement it with interfaces:
- Name.
type WithNameError interface { GetName() string SetName(string) }
- Parameters for Allure step.
type WithFields interface { GetFields() map[string]interface{} PutFields(map[string]interface{}) }
Optional assert
If assert returns an optional error, step fails but the test is successful.
You can use errors.NewOptionalError(error) method from errors package.
import ( "github.com/ozontech/cute" "github.com/ozontech/cute/errors" ) func customAssertBodyWithCustomError() cute.AssertBody { return func(bytes []byte) error { if len(bytes) == 0 { return errors.NewOptionalError("body is empty") } return nil } }
To create optional error, implement error with interface:
type OptionalError interface { IsOptional() bool SetOptional(bool) }
Global Environment Keys
| Key | Meaning | Default |
|---|---|---|
ALLURE_OUTPUT_PATH |
Path to output allure results. | . (Folder with tests) |
ALLURE_OUTPUT_FOLDER |
Name of result folder. | /allure-results |
ALLURE_ISSUE_PATTERN |
Url pattepn to issue. Must contain %s. |
|
ALLURE_TESTCASE_PATTERN |
URL pattern to TestCase. Must contain %s. |
|
ALLURE_LAUNCH_TAGS |
Default tags for all tests. Tags must be separated by commas. |









