GithubHelp home page GithubHelp logo

h2non / baloo Goto Github PK

View Code? Open in Web Editor NEW
772.0 12.0 32.0 75 KB

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

Home Page: https://godoc.org/github.com/h2non/baloo

License: MIT License

Go 99.69% Makefile 0.31%
golang http testing expectations assert http-api-debug gentleman json-schema

baloo's Introduction

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

baloo's People

Contributors

abdelrahmanbadr avatar bobisme avatar h2non avatar vehsamrak avatar zak905 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

baloo's Issues

Improvement: (optionally) print response body if assertion fails

An issue that has been bothering me for a while is that when a status assertion (it can be other assertion as well) fails, the response is not printed. Most of the time (at the least in the case of Rest APIs), the response contains a hint about why the status assertion fails, and it makes it easy to debug when the response is printed. Currently, I need to add parse and print response code everytime I need it, and it gets more annoying when tests are running in the CI. I am aware of the plugin https://github.com/izumin5210/gentleman-logger, but with the plugin the response is printed in all cases (and also it seems very old and not maintained). Example:

func TestXxx(t *testing.T) {
	baloo.New("https://httpbin.org").Post("/anything").BodyString(`{"error":"db connection failed"}`).
		Expect(t).Status(500).Done()
}

The assertion error in this case is Unexpected status code for: POST https://httpbin.org/anything => 200 != 500

To be able to see the error, one has to add some boilerplate code:

func TestXxx(t *testing.T) {
	resp, _ := baloo.New("https://httpbin.org").Post("/anything").BodyString(`{"error":"db connection failed"}`).
		Expect(t).Status(500).Send()
	fmt.Println(resp.String())
}

It would be nice if this is provided out of the box, and only when the assertion fails.

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

Ignoring the error returned by the Done() method

Hi!

A few days ago, I started using the Go linter (with Go 1.21). When running it on my Baloo tests, there were many warnings about ignoring the error returned by the Done() method.

Even when looking at the first example below:

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() // Warning: Error return value of `(gopkg.in/h2non/baloo.v3.Expect).Done` is not checked (errcheck)
}

Is there a good way to use Baloo without having the warning? One possibility would be to write this:

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) {
  err := test.Get("/get").
    SetHeader("Foo", "Bar").
    Expect(t).
    Status(200).
    Header("Server", "apache").
    Type("json").
    JSON(map[string]string{"bar": "foo"}).
    Done()
  if err != nil {
    t.Error(err)
  }
}

But then the error is reported twice and it makes writing tests less friendly/readable.

It seems that the best way to handle this would be to remove the returned error from the Done() method signature, or to provide another method that does not return the error.

Any thought? @h2non: I am willing to write a pull request if you agree with one of my propositions or if you have another one.

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

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?

Featured examples

  • Assertion composition
  • Headers
  • Status
  • Middleware
  • Gentleman specific methods

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?

Support multiple test runners

Currently only support Go's native testing package. Alternative test runners could be supported as sort of adapters.

Listing a few for future consideration:

  • Ginkgo
  • gocheck

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?

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?

.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

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?

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

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.

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

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.