httpdog

package module
v0.1.7 Latest Latest
Warning

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

Go to latest
Published: Apr 29, 2021 License: MIT Imports: 10 Imported by: 3

README

Cucumber HTTP steps for Go

Build Status Coverage Status GoDevDoc Time Tracker Code lines Comments

This module implements HTTP-related step definitions for github.com/cucumber/godog.

Steps

Local Service

Local service can be tested with client request configuration and response expectations.

Request Setup
When I request HTTP endpoint with method "GET" and URI "/get-something?foo=bar"

An additional header can be supplied. For multiple headers, call step multiple times.

And I request HTTP endpoint with header "X-Foo: bar"

An additional cookie can be supplied. For multiple cookies, call step multiple times.

And I request HTTP endpoint with cookie "name: value"

Optionally request body can be configured. If body is a valid JSON5 payload, it will be converted to JSON before use. Otherwise, body is used as is.

And I request HTTP endpoint with body
"""
[
  // JSON5 comments are allowed.
  {"some":"json"}
]
"""

Request body can be provided from file.

And I request HTTP endpoint with body from file
"""
path/to/file.json5
"""

If endpoint is capable of handling duplicated requests, you can check it for idempotency. This would send multiple requests simultaneously and check

  • if all responses are similar or (all successful like GET)
  • if responses can be grouped into exactly ONE response of a kind and OTHER responses of another kind (one successful, other failed like with POST).

Number of requests can be configured with Local.ConcurrencyLevel, default value is 10.

And I concurrently request idempotent HTTP endpoint
Response Expectations

Response expectation has to be configured with at least one step about status, response body or other responses body ( idempotency mode).

If response body is a valid JSON5 payload, it is converted to JSON before use.

JSON bodies are compared with assertjson which allows ignoring differences when expected value is set to "<ignore-diff>".

And I should have response with body
"""
[
  {"some":"json","time":"<ignore-diff>"}
]
"""
And I should have response with body from file
"""
path/to/file.json
"""

Status can be defined with either phrase or numeric code.

Then I should have response with status "OK"
Then I should have response with status "204"

And I should have other responses with status "Not Found"

In an idempotent mode you can check other responses.

And I should have other responses with body
"""
{"status":"failed"}
"""
And I should have other responses with body from file
"""
path/to/file.json
"""

Optionally response headers can be asserted.

Then I should have response with header "Content-Type: application/json"

And I should have other responses with header "Content-Type: text/plain"
And I should have other responses with header "X-Header: abc"
External Services

In simple case you can define expected URL and response.

Given "some-service" receives "GET" request "/get-something?foo=bar"

And "some-service" responds with status "OK" and body
"""
{"key":"value"}
"""

Or request with body.

And "another-service" receives "POST" request "/post-something" with body
"""
// Could be a JSON5 too.
{"foo":"bar"}
"""

Request with body from a file.

And "another-service" receives "POST" request "/post-something" with body from file
"""
_testdata/sample.json
"""

Request can expect to have a header.

And "some-service" request includes header "X-Foo: bar"

By default, each configured request is expected to be received 1 time. This can be changed to a different number.

And "some-service" request is received 1234 times

Or to be unlimited.

And "some-service" request is received several times

Response may have a header.

And "some-service" response includes header "X-Bar: foo"

Response must have status and body (body can be empty).

And "some-service" responds with status "OK" and body
"""
{"key":"value"}
"""
And "another-service" responds with status "200" and body from file
"""
_testdata/sample.json5
"""

Example Feature

Feature: Example

  Scenario: Successful GET Request
    Given "template-service" receives "GET" request "/template/hello"

    And "template-service" responds with status "OK" and body
    """
    Hello, %s!
    """

    When I request HTTP endpoint with method "GET" and URI "/?name=Jane"

    Then I should have response with status "OK"

    And I should have response with body
    """
    Hello, Jane!
    """

Documentation

Overview

Package httpdog provides HTTP-related step definitions for github.com/cucumber/godog.

Feature: Example

 Scenario: Successful GET Request
   Given "template-service" receives "GET" request "/template/hello"

   And "template-service" responds with status "OK" and body
   """
   Hello, %s!
   """

   When I request HTTP endpoint with method "GET" and URI "/?name=Jane"

   Then I should have response with status "OK"

   And I should have response with body
   """
   Hello, Jane!
   """

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type External

type External struct {
	OnError func(err error)
	Vars    *shared.Vars
	// contains filtered or unexported fields
}

External is a collection of step-driven HTTP servers to serve requests of application with mocked data.

func (*External) Add

func (e *External) Add(service string, options ...func(mock *resttest.ServerMock)) string

Add starts a mocked server for a named service and returns url.

func (*External) GetMock added in v0.1.7

func (e *External) GetMock(service string) *resttest.ServerMock

GetMock exposes mock of external service.

func (*External) RegisterSteps

func (e *External) RegisterSteps(s *godog.ScenarioContext)

RegisterSteps adds steps to godog scenario context to serve outgoing requests with mocked data.

In simple case you can define expected URL and response.

Given "some-service" receives "GET" request "/get-something?foo=bar"

And "some-service" responds with status "OK" and body
"""
{"key":"value"}
"""

Or request with body.

And "another-service" receives "POST" request "/post-something" with body
"""
// Could be a JSON5 too.
{"foo":"bar"}
"""

Request with body from a file.

And "another-service" receives "POST" request "/post-something" with body from file
"""
_testdata/sample.json
"""

Request can expect to have a header.

And "some-service" request includes header "X-Foo: bar"

By default, each configured request is expected to be received 1 time. This can be changed to a different number.

And "some-service" request is received 1234 times

Or to be unlimited.

And "some-service" request is received several times

Response may have a header.

And "some-service" response includes header "X-Bar: foo"

Response must have status and body (body can be empty).

And "some-service" responds with status "OK" and body
"""
{"key":"value"}
"""

Response body can also be defined in file.

And "another-service" responds with status "200" and body from file
"""
_testdata/sample.json5
"""

type Local

type Local struct {
	*resttest.Client
}

Local is step-driven HTTP client for application local HTTP service.

func NewLocal

func NewLocal(baseURL string) *Local

NewLocal creates an instance of step-driven HTTP client.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"

	"github.com/bool64/httpdog"
	"github.com/cucumber/godog"
)

func main() {
	external := httpdog.External{}
	templateService := external.Add("template-service")

	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, _ := http.NewRequest(http.MethodGet, templateService+"/template/hello", nil) // nolint // Handle errors.
		resp, _ := http.DefaultTransport.RoundTrip(req)                                   // nolint // Handle errors.
		tpl, _ := ioutil.ReadAll(resp.Body)                                               // nolint // Handle errors.

		_, _ = w.Write([]byte(fmt.Sprintf(string(tpl), r.URL.Query().Get("name")))) // nolint // Handle errors.
	})

	srv := httptest.NewServer(h)
	defer srv.Close()

	local := httpdog.NewLocal(srv.URL)

	suite := godog.TestSuite{
		ScenarioInitializer: func(s *godog.ScenarioContext) {
			local.RegisterSteps(s)
			external.RegisterSteps(s)
		},
		Options: &godog.Options{
			Format: "pretty",
			Strict: true,
			Paths:  []string{"_testdata/Example.feature"},
			Output: ioutil.Discard,
		},
	}

	if suite.Run() != 0 {
		fmt.Println("test failed")
	} else {
		fmt.Println("test passed")
	}

}
Output:

test passed

func (*Local) RegisterSteps

func (l *Local) RegisterSteps(s *godog.ScenarioContext)

RegisterSteps adds HTTP server steps to godog scenario context.

Request Setup

Request configuration needs at least HTTP method and URI.

When I request HTTP endpoint with method "GET" and URI "/get-something?foo=bar"

An additional header can be supplied. For multiple headers, call step multiple times.

And I request HTTP endpoint with header "X-Foo: bar"

An additional cookie can be supplied. For multiple cookie, call step multiple times.

And I request HTTP endpoint with cookie "name: value"

Optionally request body can be configured. If body is a valid JSON5 payload, it will be converted to JSON before use. Otherwise, body is used as is.

And I request HTTP endpoint with body
"""
[
 // JSON5 comments are allowed.
 {"some":"json"}
]
"""

Request body can be provided from file.

And I request HTTP endpoint with body from file
"""
path/to/file.json5
"""

If endpoint is capable of handling duplicated requests, you can check it for idempotency. This would send multiple requests simultaneously and check

  • if all responses are similar or (all successful like GET),
  • if responses can be grouped into exactly ONE response of a kind and OTHER responses of another kind (one successful, other failed like with POST).

Number of requests can be configured with `Local.ConcurrencyLevel`, default value is 10.

And I concurrently request idempotent HTTP endpoint

Response Expectations

Response expectation has to be configured with at least one step about status, response body or other responses body (idempotency mode).

If response body is a valid JSON5 payload, it is converted to JSON before use.

JSON bodies are compared with https://github.com/swaggest/assertjson which allows ignoring differences when expected value is set to `"<ignore-diff>"`.

And I should have response with body
"""
[
 {"some":"json","time":"<ignore-diff>"}
]
"""

Response body can be provided from file.

And I should have response with body from file
"""
path/to/file.json
"""

Status can be defined with either phrase or numeric code. Also you can set response header expectations.

Then I should have response with status "OK"
And I should have response with header "Content-Type: application/json"
And I should have response with header "X-Header: abc"

In an idempotent mode you can set expectations for statuses of other responses.

Then I should have response with status "204"

And I should have other responses with status "Not Found"
And I should have other responses with header "Content-Type: application/json"

And for bodies of other responses.

And I should have other responses with body
"""
{"status":"failed"}
"""

Which can be defined as files.

And I should have other responses with body from file
"""
path/to/file.json
"""

Jump to

Keyboard shortcuts

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