Expressive end-to-end HTTP API testing made easy in Go

baloo Build Status GitHub release GoDoc Coverage Status Go Report Card

Expressive and versatile end-to-end HTTP API testing made easy in Go (golang), built on top of gentleman HTTP client toolkit.

Take a look to the examples to get started.

Features

  • Versatile built-in expectations.
  • Extensible custom expectations.
  • Declarative, expressive, fluent API.
  • Response body matching and strict equality expectations.
  • Deep JSON comparison.
  • JSON Schema validation.
  • Full-featured HTTP client built on top of gentleman toolkit.
  • Intuitive and semantic HTTP client DSL.
  • Easy to configure and use.
  • Composable chainable assertions.
  • Works with Go's testing package (more test engines might be added in the future).
  • Convenient helpers and abstractions over Go's HTTP primitives.
  • Middleware-oriented via gentleman's middleware layer.
  • Extensible and hackable API.

Versions

  • v3 - Latest stable version with better JSON assertion. Uses gentleman@v2. Recommended.
  • v2 - Stable version. Uses gentleman@v2.
  • v1 - First version. Stable. Uses gentleman@v1. Actively maintained.

Installation

go get -u gopkg.in/h2non/baloo.v3

Requirements

  • Go 1.7+

Examples

See examples directory for featured examples.

Simple request expectation

package simple

import (
  "testing"

  "gopkg.in/h2non/baloo.v3"
)

// test stores the HTTP testing client preconfigured
var test = baloo.New("http://httpbin.org")

func TestBalooSimple(t *testing.T) {
  test.Get("/get").
    SetHeader("Foo", "Bar").
    Expect(t).
    Status(200).
    Header("Server", "apache").
    Type("json").
    JSON(map[string]string{"bar": "foo"}).
    Done()
}

Custom assertion function

package custom_assertion

import (
  "errors"
  "net/http"
  "testing"

  "gopkg.in/h2non/baloo.v3"
)

// test stores the HTTP testing client preconfigured
var test = baloo.New("http://httpbin.org")

// assert implements an assertion function with custom validation logic.
// If the assertion fails it should return an error.
func assert(res *http.Response, req *http.Request) error {
  if res.StatusCode >= 400 {
    return errors.New("Invalid server response (> 400)")
  }
  return nil
}

func TestBalooClient(t *testing.T) {
  test.Post("/post").
    SetHeader("Foo", "Bar").
    JSON(map[string]string{"foo": "bar"}).
    Expect(t).
    Status(200).
    Type("json").
    AssertFunc(assert).
    Done()
}

JSON Schema assertion

package json_schema

import (
  "testing"

  "gopkg.in/h2non/baloo.v3"
)

const schema = `{
  "title": "Example Schema",
  "type": "object",
  "properties": {
    "origin": {
      "type": "string"
    }
  },
  "required": ["origin"]
}`

// test stores the HTTP testing client preconfigured
var test = baloo.New("http://httpbin.org")

func TestJSONSchema(t *testing.T) {
  test.Get("/ip").
    Expect(t).
    Status(200).
    Type("json").
    JSONSchema(schema).
    Done()
}

Custom global assertion by alias

package alias_assertion

import (
  "errors"
  "net/http"
  "testing"

  "gopkg.in/h2non/baloo.v3"
)

// test stores the HTTP testing client preconfigured
var test = baloo.New("http://httpbin.org")

func assert(res *http.Response, req *http.Request) error {
  if res.StatusCode >= 400 {
    return errors.New("Invalid server response (> 400)")
  }
  return nil
}

func init() {
  // Register assertion function at global level
  baloo.AddAssertFunc("test", assert)
}

func TestBalooClient(t *testing.T) {
  test.Post("/post").
    SetHeader("Foo", "Bar").
    JSON(map[string]string{"foo": "bar"}).
    Expect(t).
    Status(200).
    Type("json").
    Assert("test").
    Done()
}

API

See godoc reference for detailed API documentation.

HTTP assertions

Status(code int)

Asserts the response HTTP status code to be equal.

StatusRange(start, end int)

Asserts the response HTTP status to be within the given numeric range.

StatusOk()

Asserts the response HTTP status to be a valid server response (>= 200 && < 400).

StatusError()

Asserts the response HTTP status to be a valid clint/server error response (>= 400 && < 600).

StatusServerError()

Asserts the response HTTP status to be a valid server error response (>= 500 && < 600).

StatusClientError()

Asserts the response HTTP status to be a valid client error response (>= 400 && < 500).

Type(kind string)

Asserts the Content-Type header. MIME type aliases can be used as kind argument.

Supported aliases: json, xml, html, form, text and urlencoded.

Header(key, value string)

Asserts a response header field value matches.

Regular expressions can be used as value to perform the specific assertions.

HeaderEquals(key, value string)

Asserts a response header field with the given value.

HeaderNotEquals(key, value string)

Asserts that a response header field is not equal to the given value.

HeaderPresent(key string)

Asserts if a header field is present in the response.

HeaderNotPresent(key string)

Asserts if a header field is not present in the response.

BodyEquals(value string)

Asserts a response body as string using strict comparison.

Regular expressions can be used as value to perform the specific assertions.

BodyMatchString(pattern string)

Asserts a response body matching a string expression.

Regular expressions can be used as value to perform the specific assertions.

BodyLength(length int)

Asserts the response body length.

JSON(match interface{})

Asserts the response body with the given JSON struct.

JSONSchema(schema string)

Asserts the response body againts the given JSON schema definition.

data argument can be a string containing the JSON schema, a file path or an URL pointing to the JSON schema definition.

Assert(alias string)

Assert adds a new assertion function by alias name.

Assertion function must be previosly registered via baloo.AddAssertFunc("alias", function).

See an example here.

AssertFunc(func (*http.Response, *http.Request) error)

Adds a new custom assertion function who should return an detailed error in case that the assertion fails.

Development

Clone this repository:

git clone https://github.com/h2non/baloo.git && cd baloo

Install dependencies:

go get -u ./...

Run tests:

go test ./...

Lint code:

go test ./...

Run example:

go test ./_examples/simple/simple_test.go

License

MIT - Tomas Aparicio

Comments
  • support asserting JSON types other than objects (fixes #16)

    support asserting JSON types other than objects (fixes #16)

    The JSON matching is is adjusted to compared marshalled values instead of bytes because the order of JSON object keys is non deterministic and therefore can cause comparisons to fail.

  • failed to Unmarshal: invalid character '\n' in string literal

    failed to Unmarshal: invalid character '\n' in string literal

    Hello, when trying to validate the JSON object returned baloo will fail if a string contains a newline. For example:

    func TestATest(t *testing.T) {
    	testURL.
    		Post("/api/v1/endpoint/").
    		Expect(t).
    		Status(500).
    		Type("json").
    		JSON("{}").
    		Done()
    }
    

    Will break with the following error:

    --- FAIL: TestATest (0.02s)
    	expect.go:199: failed to Unmarshal: invalid character '\n' in string literal
    FAIL
    

    If the JSON blob returned is:

    {"a_valid_string": "asdadasd\nasdasdasdasd"}
    
  • The error is not accurately describe

    The error is not accurately describe

    Very nice library! But there is one drawback.

    Run this test

    package baloo
    
    import (
    	"gopkg.in/h2non/baloo.v3"
    	"testing"
    )
    
    func Test(t *testing.T) {
    	b := baloo.New("http://www.ya.ru")
    	_ = b.Get(".").Expect(t).Status(11).Done()
    	_ = b.Get(".").Expect(t).Status(11).Done()
    	_ = b.Get(".").Expect(t).Status(11).Done()
    }
    

    Look result

    === RUN   Test
    --- FAIL: Test (0.35s)
        expect.go:204: Unexpected status code: 404 != 11
        expect.go:204: Unexpected status code: 404 != 11
        expect.go:204: Unexpected status code: 404 != 11
    FAIL
    
    Process finished with exit code 1
    

    There is no way to know exactly where the test failed. The error message point inside the library

  • .JSON function changes request from GET to POST

    .JSON function changes request from GET to POST

    In the following code snippet the .JSON function changes the GET request to a POST request. I found this very confusing, is this expected?

    var test = baloo.New("http://httpbin.org")
    func TestBalooClient(t *testing.T) {
    	test.Get("/get").
    		JSON(map[string]string{"foo": "bar"}).
    		Expect(t).
    		Status(200).
    		Type("json").
    		Done()
    }
    
  • Ignore SSL cert errors

    Ignore SSL cert errors

    Is there a way to get Baloo to ignore SSL certificate errors. I'm using a self-signed SSL certificate and my tests are generating errors like:

    FAIL: TestUsers_List/Not_authenticated (0.35s)
    expect.go:192: request error: Get https://core.myapp.test/v1/users: x509: certificate signed by unknown authority
    FAIL: TestUsers_List/Authenticated_but_not_admin (0.11s)
    expect.go:192: request error: Get https://core.myapp.test/v1/users: x509: certificate signed by unknown authority
    

    When making HTTP requests in Go, it's possible to ignore SSL errors by configuring the Transport property like so:

    tr := http.Transport{
    	TLSClientConfig: &tls.Config{
    		InsecureSkipVerify: true,
    	},
    }
    client := http.Client{}
    client.Transport = &tr
    

    But where would I do this in baloo?

  • Example doesen't work

    Example doesen't work

    The following example doesn't work for me. In particular the expected json is null. Also httpbin now uses nginx instead of apache.

    package simple
    
    import (
      "testing"
    
      "gopkg.in/h2non/baloo.v1"
    )
    
    // test stores the HTTP testing client preconfigured
    var test = baloo.New("http://httpbin.org")
    
    func TestBalooSimple(t *testing.T) {
      test.Get("/get").
        SetHeader("Foo", "Bar").
        Expect(t).
        Status(200).
        Header("Server", "apache").
        Type("json").
        JSON(map[string]string{"bar": "foo"}).
        Done()
    }
    
  • Data race in baloo / gentleman

    Data race in baloo / gentleman

    WARNING: DATA RACE
    Write at 0x00c42035d090 by goroutine 44:
      github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo.v0/assert.readBody()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo.v0/assert/body.go:18 +0x250
      github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo.v0/assert.BodyMatchString.func1()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo.v0/assert/body.go:26 +0x6c
      github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo%2ev0.(*Expect).run()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo.v0/expect.go:213 +0x93
      github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo%2ev0.(*Expect).Done()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/gopkg.in/h2non/baloo.v0/expect.go:197 +0x257
      github.com/stratexio/wapi/pkg/api/tokens_test.TestCreate.func1.2.2()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/pkg/api/tokens/create_test.go:169 +0xfe5
      github.com/stratexio/wapi/vendor/github.com/franela/goblin.runIt.func1()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/github.com/franela/goblin/goblin.go:191 +0x34
    
    Previous read at 0x00c42035d090 by goroutine 5:
      github.com/stratexio/wapi/vendor/gopkg.in/h2non/gentleman%2ev1.EnsureResponseFinalized.func1()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/gopkg.in/h2non/gentleman.v1/response.go:232 +0x6d
    
    Goroutine 44 (running) created at:
      github.com/stratexio/wapi/vendor/github.com/franela/goblin.runIt()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/github.com/franela/goblin/goblin.go:191 +0x277
      github.com/stratexio/wapi/vendor/github.com/franela/goblin.(*It).run()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/github.com/franela/goblin/goblin.go:128 +0xdf
      github.com/stratexio/wapi/vendor/github.com/franela/goblin.(*Describe).run()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/github.com/franela/goblin/goblin.go:88 +0x18b
      github.com/stratexio/wapi/vendor/github.com/franela/goblin.(*Describe).run()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/github.com/franela/goblin/goblin.go:88 +0x18b
      github.com/stratexio/wapi/vendor/github.com/franela/goblin.(*G).Describe()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/vendor/github.com/franela/goblin/goblin.go:34 +0x2b9
      github.com/stratexio/wapi/pkg/api/tokens_test.TestCreate()
          /home/ubuntu/.go_workspace/src/github.com/stratexio/wapi/pkg/api/tokens/create_test.go:205 +0x180
      testing.tRunner()
          /usr/local/go/src/testing/testing.go:610 +0xc9
    
    Goroutine 5 (running) created at:
      runtime.createfing()
          /usr/local/go/src/runtime/mfinal.go:139 +0x72
      os.init()
          /usr/local/go/src/os/file.go:54 +0x399
      main.init()
          github.com/stratexio/wapi/pkg/api/tokens/_test/_testmain.go:120 +0x9d
    ==================```
    
    Not sure what to do with it :/ I'd rather not switch away from your library.
  • Codecoverage

    Codecoverage

    I'm trying to test Baloo to test my project's functionality. It works fine, but when running code coverage like so;

    go test -v -covermode=atomic -coverprofile=coverage.out -run TestBalooSimple
    

    I get coverage: 0.0% of statements.

    Maybe I'm doing it wrong with the command? Or is it running separately and won't work like this? Shouldn't coverage see that while the server is running (in the same code), that the Baloo-visits are actually hitting/covering code?

  • assert.JSON: support other JSON values than objects

    assert.JSON: support other JSON values than objects

    Hi,

    I was wondering: do you have a particular reason for having assert.unmarshalBody() return map[string]interface{}? Of course usually you deal with JSON objects, but JSON has many valid values (http://www.json.org):

    • string
    • number
    • object
    • array
    • boolean
    • null

    I noticed this restriction because I ran into problems with assert a body using map[string]string (which doesn't work, because assert.compare assumes map[string]interface{} is passed in the assertion. I can imagine that this is too strict and can be relaxed? E.g., checking if the value is a slice, struct, map, or implements json.Marshaler? For example, gentleman uses the following logic:

                    // var data interface{}
    		switch data.(type) {
    		case string:
    			buf.WriteString(data.(string))
    		case []byte:
    			buf.Write(data.([]byte))
    		default:
    			if err := json.NewEncoder(buf).Encode(data); err != nil {
    				h.Error(ctx, err)
    				return
    			}
    		}
    

    Would you accept a PR for this?

  • Register custom expectations globally

    Register custom expectations globally

    // assert implements an assertion function with custom validation logic.
    // If the assertion fails it should return an error.
    func assert(res *http.Response, req *http.Request) error {
      if res.StatusCode >= 400 {
        return errors.New("Invalid server response (> 400)")
      }
      return nil
    }
    
    func TestBalooClient(t *testing.T) {
      baloo.AddAssertFunc('response.headers', assert)
    
      test.Post("/post").
        SetHeader("Foo", "Bar").
        JSON(map[string]string{"foo": "bar"}).
        Expect(t).
        Status(200).
        Type("json").
        Assert('response.headers').
        Done()
    }
    
  • Fix wrong file in logged message when failure occurs

    Fix wrong file in logged message when failure occurs

    This fixes #28 However I did not find a way to completely customize the logged message because the testing package does not allow such thing, (see https://github.com/golang/go/issues/37708)

    The printed message shows now where the error occurs in the calling code, however expect.go:213: will still appear as a prefix to the error message, for example:

    expect.go:213: http_test.go:12: Unexpected status code: 400 != 11

  • Extract Response Body?

    Extract Response Body?

    I have several test cases that perform JSON asserations. However, I need to extract specific fields from a JSON response that will be used for subsequent business logic.

    How does one extract values from the response body?

  • Baloo overwrites `Transfer-Encoding` header

    Baloo overwrites `Transfer-Encoding` header

    When I provide:

    t.Post("/v1/identifiers/druids").
    		SetHeader("Transfer-Encoding", "identity").
    		Expect(t).
    		Status(200).
    		Type("json").
    		JSON(map[string]string{"status": "OK"}).
    		Done()
    
    

    When I look at the packets (via wireshark) I see that my header has been overwritten by: Transfer-Encoding: chunked

  • 308 for Get, Delete and Post requests

    308 for Get, Delete and Post requests

    The following request as a Get or Post will always return a 308 if I do not set the Body as nil.

    		SetHeader("Authorization", bearerGithubToken()).
    		Body(nil).
    		Expect(t).
    		Status(200).
    		Done()```
    
    On a Post I always get a 308.
    ``baseURL.Post("/v1/definition/stack").
    		SetHeader("Authorization", bearerGithubToken()).
    		JSON(app).
    		Expect(t).
    		Status(200).
    		Done()```
    
    Each of these requests works correctly outside the baloo libraries. I have compared the request struct and cannot figure out the difference.
    
    Any suggestions?
    
  • fix some missing log informations and add VerifyJSON assert method

    fix some missing log informations and add VerifyJSON assert method

    • fix line source code stackstrace in error (check log.go)
    • fix some compare data on error
    • fix missing refill body in JSON unmarshall
    • add VerifyJSON assert method (check JSON example) to have access json directly and test it
    • add dump of response on error
End-to-end HTTP and REST API testing for Go.

httpexpect Concise, declarative, and easy to use end-to-end HTTP and REST API testing for Go (golang). Basically, httpexpect is a set of chainable bui

Jan 5, 2023
🚀🌏 Orbital is a simple end-to-end testing framework for Go

Orbital is a test framework which enables a developer to write end to end tests just like one would writing unit tests. We do this by effectively copying the testing.T API and registering tests to be run periodically on a configured schedule.

Nov 18, 2022
An always-on framework that performs end-to-end functional network testing for reachability, latency, and packet loss

Arachne Arachne is a packet loss detection system and an underperforming path detection system. It provides fast and easy active end-to-end functional

Dec 31, 2022
HTTP traffic mocking and testing made easy in Go ༼ʘ̚ل͜ʘ̚༽

gock Versatile HTTP mocking made easy in Go that works with any net/http based stdlib implementation. Heavily inspired by nock. There is also its Pyth

Jan 4, 2023
Database testing made easy in Go.

dbtest Database testing made easy in Go. Features Declarative Define the minimum test specification in a YAML-based DSL, then all tests can be generat

Oct 31, 2022
End to end functional test and automation framework
End to end functional test and automation framework

Declarative end to end functional testing (endly) This library is compatible with Go 1.12+ Please refer to CHANGELOG.md if you encounter breaking chan

Jan 6, 2023
Rr-e2e-tests - Roadrunner end-to-end tests repository
Rr-e2e-tests - Roadrunner end-to-end tests repository

RoadRunner end-to-end plugins tests License: The MIT License (MIT). Please see L

Dec 15, 2022
HTTP mock for Golang: record and replay HTTP/HTTPS interactions for offline testing

govcr A Word Of Warning I'm in the process of partly rewriting govcr to offer better support for cassette mutations. This is necessary because when I

Dec 28, 2022
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.
siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

siusiu (suite-suite harmonics) a suite used to manage the suite, designed to free penetration testing engineers from learning and using various security tools, reducing the time and effort spent by penetration testing engineers on installing tools, remembering how to use tools.

Dec 12, 2022
A yaml data-driven testing format together with golang testing library

Specimen Yaml-based data-driven testing Specimen is a yaml data format for data-driven testing. This enforces separation between feature being tested

Nov 24, 2022
Quick and Easy server testing/validation
Quick and Easy server testing/validation

Goss - Quick and Easy server validation Goss in 45 seconds Note: For an even faster way of doing this, see: autoadd Note: For testing docker container

Oct 7, 2022
Golang HTTP client testing framework

flute Golang HTTP client testing framework Presentation https://speakerdeck.com/szksh/flute-golang-http-client-testing-framework Overview flute is the

Sep 27, 2022
Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs
Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs

go-testdeep Extremely flexible golang deep comparison, extends the go testing package. Latest news Synopsis Description Installation Functions Availab

Dec 22, 2022
HTTP load testing tool and library. It's over 9000!
HTTP load testing tool and library. It's over 9000!

Vegeta Vegeta is a versatile HTTP load testing tool built out of a need to drill HTTP services with a constant request rate. It can be used both as a

Jan 7, 2023
Ditto is a CLI testing tool that helps you verify if multiple HTTP endpoints have the same outputs.

Ditto is a CLI testing tool that helps you verify if multiple HTTP endpoints have the same outputs.

Nov 24, 2021
Tesuto - a little library for testing against HTTP services

tesuto import "github.com/guregu/tesuto" tesuto is a little library for testing

Jan 18, 2022
Client tool for testing HTTP server timeouts

HTTP timeout test client While testing Go HTTP server timeouts I wrote this little tool to help me test. It allows for slowing down header write and b

Sep 21, 2022
API testing framework inspired by frisby-js
API testing framework inspired by frisby-js

frisby REST API testing framework inspired by frisby-js, written in Go Proposals I'm starting to work on frisby again with the following ideas: Read s

Sep 27, 2022
Testing API Handler written in Golang.

Gofight API Handler Testing for Golang Web framework. Support Framework Http Handler Golang package http provides HTTP client and server implementatio

Dec 16, 2022