HTTP and REST API testing for Go using Allure reports.
- Expressive and intuitive syntax.
- Built-in JSON support.
- Custom asserts.
- One step to BDD.
- Allure reports.
- Create a request and write assets.
- Run tests.
- Check Allure reports.
go get -u github.com/ozontech/cute
- Go 1.17+
Run example.
make example
To view detailed test reports, install Allure framework. It's optional.
Learn more about Allure reports
brew install allure
Run Allure.
allure serve ./examples/allure-results
See Examples directory for featured examples.
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", "[email protected]"),
json.Present("$[1].name"),
).
ExecuteTest(context.Background(), t)
}
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 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.Suite
and*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", "[email protected]"),
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.
You can create a table test in 2 ways. They'll have the same Allure reports.
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", "[email protected]"),
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)
}
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", "[email protected]"),
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.
You can create your own asserts or use ready-made from the package asserts.
Equal
is a function to assert that a JSONPath expression matches the given value.NotEqual
is a function to check that a JSONPath expression value isn't equal to the given value.Length
is a function to assert that value is the expected length.GreaterThan
is a function to assert that value is greater than the given length.GreaterOrEqualThan
is a function to assert that value is greater or equal to the given length.LessThan
is a function to assert that value is less than the given length.LessOrEqualThan
is a function to assert that value is less or equal to the given length.
Present
is a function to assert that value is present. Value can be 0 or null.NotEmpty
is a function to assert that value is present and not empty. Value can't be 0 or null.NotPresent
is a function to assert that value isn't present.Diff
is a function to compare two JSONs.Contains
is a function to assert that a JSONPath expression extracts a value in an array.EqualJSON
is a function to check that a JSON path expression value is equal to given JSON.NotEqualJSON
is a function to check that a JSONPath expression value isn't equal to given JSON.GetValueFromJSON
is a function for getting a value from a JSON.
Learn more about asserts implementation
Present
is a function to assert that header is present.NotPresent
is a function to assert that header isn't present.
Learn more about asserts implementation
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.
You can implement 3 type of asserts:
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
}
}
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
}
}
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{})
}
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)
}
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. |