Skip to content

ozontech/cute

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cute

CUTE — create your tests easily

HTTP and REST API testing for Go.

Three steps for testing your HTTP service:

  1. Create request and write assets
  2. Run tests
  3. Check allure

Head of contents:

  1. Head of contents
  2. Installation
  3. Features
  4. Demo
  5. Examples
    1. Single test with Allure
    2. Suite tests
    3. Multi-step test
    4. Table tests
      1. Builder
      2. Array
  6. Asserts
    1. JSON asserts
    2. Headers asserts
    3. JSON schema
    4. Custom asserts
      1. Base
      2. T
      3. Errors
  7. Global Environment Keys

Installation

  go get -u github.com/ozontech/cute

Requirements

  • Go 1.17+

Features

  • Full integration with Allure
  • Expressive and intuitive syntax
  • Built-in JSON support
  • Custom asserts
  • One step to BDD

Demo

  1. Install allure
  brew install allure
  1. Run example
 make example
  1. Run allure
  allure serve ./examples/allure-results

Examples

See examples directory for featured examples.

See an example of creating a single test.
For a result with allure information you can 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)
}

See full example here

Allure:

img.png

Suite provides a structure in which you can describe tests by grouping them into test suites. This can be useful if you have a lot of different tests and it is difficult to navigate through them without having additional "layers nesting levels" of test calls.

You can read about Allure.Suite here

  1. 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 %v", err)
    }

    i.host = host
}
  1. Declare test
import (
    "github.com/ozontech/allure-go/pkg/framework/suite"
)

func TestExampleTest(t *testing.T) {
    suite.RunSuite(t, new(ExampleSuite))
}
  1. Just relax and 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 here

Allure:

one_step.png

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 example here

Allure:

multistep_test.png

One step to table tests...

You have 2 ways to create table test. These ways have 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", "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)
}
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 here

Common allure for all table tests:

Report has 2 different tests/suites:

table_tests_execute_array.png

Main report:

table_tests_execute_array_test_1.png

You can create your own asserts or use ready-made asserts from the package asserts

You can find implementation here

  • Equal is a function to assert that a jsonpath expression matches the given value
  • NotEqual is a function to check that jsonpath expression value is not 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 than 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 than 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 is not present

See implementation here

  • Present is a function to assert that header is present
  • NotPresent is a function to assert that header is not present

There are three ways to validate a JSON Schema. It all depends on where you have it.

  • ExpectJSONSchemaString(string) - is a function for compares a JSON schema from a string.
  • ExpectJSONSchemaByte([]byte) - is a function for compares a JSON schema from an array of bytes.
  • ExpectJSONSchemaFile(string) - is a function for compares a JSON schema from a file or remote resource.

Allure:

img.png

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
    }
}

Types for creating custom assertions using Allure Actions and testing.TB.
You can log some information to Allure.
Also you can log error on Allure yourself or just return 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 step creations:

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
    }
}

Allure:

custom_assert.png

You can use method errors.NewAssertError from package errors:

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
    }
}

If you'd like to create a pretty error in your custom assert you should implement error with interfaces:

With name
type WithNameError interface {
    GetName() string
    SetName(string)
}

With parameters for allure step

type WithFields interface {
    GetFields() map[string]interface{}
    PutFields(map[string]interface{})
}

Allure:

assert_error.png

Optional assert

If assert returns optional error step will be failed but test will be success.

You can use method errors.NewOptionalError(error) from package errors:

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 you should implement error with interface

type OptionalError interface {
    IsOptional() bool
    SetOptional(bool)
}

Allure:

optional_error.png

Key Meaning Default
ALLURE_OUTPUT_PATH Path to output allure results . (Folder with tests)
ALLURE_OUTPUT_FOLDER Name 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.