rehapt

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 16, 2021 License: MIT Imports: 16 Imported by: 0

README

rehapt Build Status Coverage Status GoDoc reference

Rehapt is a Golang declarative REST HTTP API testing library. You describe how you expect your HTTP API to behave and the library take care of comparing the expected and actual response elements.

This library has been designed to work very well for JSON APIs but it can handle any other format as long as it marshal/unmarshal to/from simple maps and slices

Features

  • Support Go v1.7+
  • Work on http.Handler, no need to really start your http server
  • Describe the request you want to perform
  • Describe the object you expect as response
  • Easy to configure
  • Define default headers (useful for auth for example)
  • Include a variable system to extract values from API responses and reuse them later
  • Smart response expectations:
    • Ignore some fields
    • Regexp string expectation
    • Expect times with delta
    • Expect numbers with delta
    • Store fields in variables
    • Load variables previously stored
    • Unsorted slice expectation if order doesn't matter
    • Partial map expectation when only some keys matter
  • No third-party dependencies

Installation

go get github.com/thib-ack/rehapt

Examples

See examples folder for more examples.

Simple example
package example

import (
    . "github.com/thib-ack/rehapt"
    "net/http"
    "testing"
)

func TestAPISimple(t *testing.T) {
    r := NewRehapt(t, yourHttpServerMux)

    // Each testcase consist of a description of the request to execute
    // and a description of the expected response
    // By default the response description is exhaustive. 
    // If an actual response field is not listed here, an error will be triggered
    // of course if an expected field described here is not present in response, an error will be triggered too.
    r.TestAssert(TestCase{
        Request: TestRequest{
            Method: "GET",
            Path:   "/api/user/1",
        },
        Response: TestResponse{
            Code: http.StatusOK,
            Object: M{
                "id":   "1",
                "name": "John",
                "age":  51,
                "pets": S{ // S for slice, M for map. Easy right ?
                    M{
                        "id":   "2",
                        "name": "Pepper the cat",
                        "type": "cat",
                    },
                },
                "weddingdate": "2019-06-22T16:00:00.000Z",
            },
        },
    })
}
Advanced examples

Obviously more advanced features are supported:

package example

import (
    . "github.com/thib-ack/rehapt"
    "net/http"
    "testing"
    "time"
)

func TestAPIAdvanced(t *testing.T) {
    r := NewRehapt(t, yourHttpServerMux)

    r.TestAssert(TestCase{
        Request: TestRequest{
            Method: "GET",
            // Add headers to request. (default headers are also supported)
            Headers: H{"X-Custom": []string{"my value"}},
            Path:   "/api/user/1",
        },
        Response: TestResponse{
            Code: http.StatusOK,
            // Check for headers presence in response
            Headers: PartialM{"X-Pet-Type": S{"Cat"}},
            Object: M{
                "id":   "1",
                // We can ignore a specific field
                "name": Any,
                // We can expect numbers with a given delta
                "age":  NumberDelta{Value: 50, Delta: 10},
                "pets": S{
                    // We can expect a partial map. 
                    // the keys not listed here will be ignored instead of returned as missing
                    PartialM{
                        "id":   "2",
                        // We can expect with regexp
                        "name": Regexp(`[A-Za-z]+ the cat`),
                        "type": "cat",
                        // We can expect a slice without order constraint
                        // here, ["mouse", "ball"] and ["ball", "mouse"] are valid responses
                        "toys": UnsortedS{"mouse", "ball"},
                    },
                },
                // We can expect dates with a given delta
                "weddingdate": TimeDelta{Time: time.Now(), Delta: 24 * time.Hour},
            },
        },
    })
}

Rehapt also includes a variable system, used to extract values from API responses and use them in later API calls.

package example

import (
    "fmt"
    . "github.com/thib-ack/rehapt"
    "net/http"
    "testing"
)

func TestAPIVariables(t *testing.T) {
    r := NewRehapt(t, yourHttpServerMux)

    r.TestAssert(TestCase{
        Request: TestRequest{
            Method: "GET",
            Path:   "/api/user/1",
        },
        Response: TestResponse{
            Code: http.StatusOK,
            Object: M{
                // StoreVar doesn't check the actual value but store it in a variable named "age" here
                "age":  StoreVar("age"),
                "pets": S{
                    M{
                        // This shortcut act like a StoreVar("catid")
                        "id":   "$catid$",
                    },
                },
            },
        },
    })

    // And we can reuse the variables in a next API call
    r.TestAssert(TestCase{
        Request: TestRequest{
            Method: "GET",
            // Here we indicate to use the variable catid value. 
            // for example this will call /api/cat/2 if value in previous request was 2
            Path:   "/api/cat/_catid_",
        },
        Response: TestResponse{
            Code: http.StatusOK,
            Object: M{
                // LoadVar load the variable value and check with actual response value.
                // Here it will report an error if cat's age is not the same as John's age
                // which were extracted from previous request
                "age":  LoadVar("age"),
            },
        },
    })

    // Or we can play with the variables if we need
    fmt.Println("Its age is ", r.GetVariable("age"))
    // We can also define them by hand. Any type of value is supported
    r.SetVariable("myvar", M{"key": "value"})
}

License

MIT - Thibaut Ackermann

Documentation

Overview

Package rehapt allows to build REST HTTP API test cases by describing the request to execute and the expected response object. The library takes care of comparing the expected and actual response and reports any errors. It has been designed to work very well for JSON APIs

Example:

func TestAPISimple(t *testing.T) {
  r := NewRehapt(t, yourHttpServerMux)

  // Each testcase consist of a description of the request to execute
  // and a description of the expected response
  // By default the response description is exhaustive.
  // If an actual response field is not listed here, an error will be triggered
  // of course if an expected field described here is not present in response, an error will be triggered too.
  r.TestAssert(TestCase{
      Request: TestRequest{
          Method: "GET",
          Path:   "/api/user/1",
      },
      Response: TestResponse{
          Code: http.StatusOK,
          Object: M{
              "id":   "1",
              "name": "John",
              "age":  51,
              "pets": S{ // S for slice, M for map. Easy right ?
                  M{
                      "id":   "2",
                      "name": "Pepper the cat",
                      "type": "cat",
                  },
              },
              "weddingdate": "2019-06-22T16:00:00.000Z",
          },
      },
  })
}

See https://github.com/thib-ack/rehapt/tree/master/examples for more examples

Index

Constants

View Source
const Any = any("{Any}")

Any allow you to ignore completely the field

View Source
const AnyCode = -1

AnyCode allow you to ignore completely the response code

Variables

This section is empty.

Functions

This section is empty.

Types

type ErrorHandler

type ErrorHandler interface {
	Errorf(format string, args ...interface{})
}

ErrorHandler is the interface used to report errors when found by TestAssert(). Note that *testing.T implements this interface

type H

type H map[string][]string

H declare a Headers map. It is used to quickly define Headers within your requests

type LoadVar

type LoadVar string

LoadVar allow to load the value of the variable and then compare with actual value

type M

type M map[string]interface{}

M declare a Map. It is used to quickly build a map within your expected response body

type NumberDelta

type NumberDelta struct {
	Value float64
	Delta float64
}

NumberDelta allow to expect number value with a given +/- delta. Delta is compared to math.Abs(expected - actual) which explain why if your expected value is 10 with a delta of 3, actual value will match from 7 to 13.

type PartialM

type PartialM map[string]interface{}

PartialM declare a Partial Map. It is used to expect some fields but ignore the un-listed ones instead of reporting missing

type Regexp

type Regexp string

Regexp allow to do advanced regexp expectation. If the regexp is invalid, an error is reported. If the actual value to compare with is not a string, an error is reported. If the actual value does not match the regexp, an error is reported

type RegexpVars

type RegexpVars struct {
	Regexp string
	Vars   map[int]string
}

RegexpVars is a mix between Regexp and StoreVar. It check if the actual value matches the regexp. but all the groups defined in the regexp can be extracted to variables for later reuse The Vars hold the mapping groupid: varname. For example with Regexp: `^Hello (.*) !$` and Vars: map[int]string{0: "all", 1: "name"} then if the actual value is "Hello john !", it will match and 2 vars will be stored:

"all" = "Hello john !"  (group 0 is the full match)
"name" = "John"

type Rehapt

type Rehapt struct {
	// contains filtered or unexported fields
}

Rehapt - REST HTTP API Test

This is the main structure of the library. You can build it using the NewRehapt() function.

func NewRehapt

func NewRehapt(errorHandler ErrorHandler, handler http.Handler) *Rehapt

NewRehapt build a new Rehapt instance from the given http.Handler. `handler` must be your server global handler. For example it could be a simple http.NewServeMux() or an complex third-party library mux. `errorHandler` can be the *testing.T parameter of your test, if value is nil, the errors are printed on stdout

func (*Rehapt) AddDefaultHeader added in v0.2.0

func (r *Rehapt) AddDefaultHeader(name string, value string)

AddDefaultHeader allow to add a default request header. This header will be added to all requests, however each TestCase can override its value

func (*Rehapt) GetDefaultHeader

func (r *Rehapt) GetDefaultHeader(name string) string

GetDefaultHeader returns the default request header value from its name. Default headers are added automatically to all requests

func (*Rehapt) GetDefaultHeaders added in v0.2.0

func (r *Rehapt) GetDefaultHeaders() http.Header

GetDefaultHeaders allow to get all default request headers. These headers will be added to all requests, however each TestCase can override their values

func (*Rehapt) GetVariable

func (r *Rehapt) GetVariable(name string) interface{}

GetVariable allow to retrieve a variable value from its name. nil is returned if variable is not found

func (*Rehapt) GetVariableString

func (r *Rehapt) GetVariableString(name string) string

GetVariableString allow to retrieve a variable value as a string from its name empty string is returned if variable is not found

func (*Rehapt) SetDefaultHeader

func (r *Rehapt) SetDefaultHeader(name string, value string)

SetDefaultHeader allow to set a default request header. This header will be added to all requests, however each TestCase can override its value

func (*Rehapt) SetDefaultHeaders added in v0.2.0

func (r *Rehapt) SetDefaultHeaders(headers http.Header)

SetDefaultHeaders allow to set all default request headers. These headers will be added to all requests, however each TestCase can override their values

func (*Rehapt) SetDefaultTimeDeltaFormat

func (r *Rehapt) SetDefaultTimeDeltaFormat(format string)

SetDefaultTimeDeltaFormat allow to change the default time format It is used by TimeDelta, to parse the actual string value as a time.Time Default is set to time.RFC3339 which is ok for JSON. This default format can be changed manually for each TimeDelta

func (*Rehapt) SetErrorHandler

func (r *Rehapt) SetErrorHandler(errorHandler ErrorHandler)

SetErrorHandler allow to change the object handling errors which is called when TestAssert() encounter an error. Setting ErrorHandler to nil will simply print the errors on stdout

func (*Rehapt) SetHttpHandler

func (r *Rehapt) SetHttpHandler(handler http.Handler)

SetHttpHandler allow to change the http.Handler used to run requests

func (*Rehapt) SetLoadShortcutBounds

func (r *Rehapt) SetLoadShortcutBounds(prefix string, suffix string) error

SetLoadShortcutBounds modify the strings used as prefix and suffix to identify a shortcut version of the load variable operation. The default prefix and suffix is "_" which makes the default shortcut form like "_myvar_".

func (*Rehapt) SetLoadShortcutFloatPrecision

func (r *Rehapt) SetLoadShortcutFloatPrecision(precision int)

SetLoadShortcutFloatPrecision change the precision of float formatting when used with a load shortcut. For example "value is _myvar_" can be replaced by "value is 10.50" or "value is 10.500000".

func (*Rehapt) SetMarshaler

func (r *Rehapt) SetMarshaler(marshaler func(v interface{}) ([]byte, error))

SetMarshaler allow to change the marshal function used to encode requests body. The default marshaler is json.Marshal

func (*Rehapt) SetStoreShortcutBounds

func (r *Rehapt) SetStoreShortcutBounds(prefix string, suffix string) error

SetStoreShortcutBounds modify the strings used as prefix and suffix to identify a shortcut version of the store variable operation. The default prefix and suffix is "$" which makes the default shortcut form like "$myvar$".

func (*Rehapt) SetUnmarshaler

func (r *Rehapt) SetUnmarshaler(unmarshaler func(data []byte, v interface{}) error)

SetUnmarshaler allow to change the unmarshal function used to decode requests response. The default unmarshaler is json.Unmarshal

func (*Rehapt) SetVariable

func (r *Rehapt) SetVariable(name string, value interface{}) error

SetVariable allow to define manually a variable. Variable names are strings, however values can be any type

func (*Rehapt) Test

func (r *Rehapt) Test(testcase TestCase) error

Test is the main function of the library it executes a given TestCase, i.e. do the request and check if the actual response is matching the expected response

func (*Rehapt) TestAssert

func (r *Rehapt) TestAssert(testcase TestCase)

TestAssert works exactly like Test except it reports the error if not nil using the ErrorHandler Errorf() function

type S

type S []interface{}

S declare a Slice. It is used to quickly build a slice within your expected response body

type StoreVar

type StoreVar string

StoreVar allow to store the actual value in a variable instead of checking its content

type TestCase

type TestCase struct {
	Request  TestRequest
	Response TestResponse
}

TestCase is the base type supported to describe a test. It is the object taken as parameters in Test() and TestAssert()

type TestRequest

type TestRequest struct {
	Method                       string
	Path                         string
	Headers                      H
	Body                         interface{}
	RawBody                      io.Reader
	NoPathVariableReplacement    bool
	NoHeadersVariableReplacement bool
	NoRawBodyVariableReplacement bool
}

TestRequest describe the request to be executed

type TestResponse

type TestResponse struct {
	Headers interface{}
	Code    int
	Object  interface{}
	RawBody interface{}
}

TestResponse describe the response expected

type TimeDelta

type TimeDelta struct {
	Time   time.Time
	Delta  time.Duration
	Format string
}

TimeDelta allow to expect time value with a given +/- delta. Delta is compared to math.Abs(expected - actual) which explain why if your expected time is time.Now() with a delta of 10sec, actual value will match from now-10sec to now+10sec

type UnsortedS

type UnsortedS []interface{}

UnsortedS declare an Unsorted Slice. It allow to expect a list of element but without the constraint of order matching

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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