cucumboa

package module
v0.0.0-...-9719495 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2022 License: MIT Imports: 13 Imported by: 0

README

Cucumboa

Helpers for using cucumber to describe OpenAPI schemas in go


Why?

OpenApi schemas are great at describing the structure of your API. You can list all the possible response status codes for your endpoints and the data models they might return, but that's where it stops. They don't describe the API's behaviour. In what situations would a 404 status be returned? Which model from an anyOf schema will I receive if I pass an invalid query param?

Behavioural tests help to solve this problem. By using cucumber to describe the behaviours of the API service it allows us to document these behaviours in a way that is both easy to understand as a consumer but also easy to generate tests for to automatically validate the implementation.

Example

Scenario: Testing the GetPet endpoint

    Feature: Successfully retrieving a Pet
        Given pet '1234' exists
        When the 'Find pet by ID' operation is called with path params:
            | petId | 1234 |
        Then the response status will be '200'

    Feature: Invalid Id supplied
        When the 'Find pet by ID' operation is called with path params:
            | petId |  this_isnt_a_number |
        Then the response status will be '400'

    Feature: Requesting a nonexistant pet
        Given pet '9876' does not exist
        When the 'Find pet by ID' operation is called with path params:
            | petId |  9876 |
        Then the response status will be '404'

Installation

go get github.com/obfu5c8/cucumboa

Getting Started

// spec_test.go


// Load our OpenApi schema
var schema = cucumboa.MustLoadOpenApiSchemaFromFile("../../../api/openapi.yml")


func initializeScenario(ctx *godog.ScenarioContext) {

	// Create the API http.Handler from our server implementation
	httpHandler := myApi.NewHttpHandler()

	// Create a dispatcher to allow cucumboa to send requests to our API
	dispatcher := cucumboa.CreateHandlerDispatcher(httpHandler)

	// Initialise cucumboa against the scenario
	cucumboa.InitializeScenario(ctx, cucumboa.Options{
		Schema:     schema,
		Dispatcher: dispatcher,
	})
}

func TestApiSpec(t *testing.T) {

	suite := godog.TestSuite{
		ScenarioInitializer: InitializeScenario,
		Options: &godog.Options{
			Format:   "pretty",
			Paths:    []string{"./example.feature"},
			TestingT: t, // Testing instance that will run subtests.
		},
	}

	if suite.Run() != 0 {
		t.Fatal("non-zero status returned, failed to run feature tests")
	}
}

Built-in Steps

Request configuration
... the {name} operation is called
... the {name} operation is called with {param}: '{value}'
... the {name} operation is called with path params: <table>
Response Assertions
... the response status will be '{code}'
... the content will have values: <table>

Extending with a DSL

While cucumboa provides some great built-in steps for testing any generic OpenApi service, sometimes they can seem a bit verbose and clutter the view of the specification. To make the spec cleaner to read you can also extend the provided steps to create a DSL for your service.

Example
Scenario: Comparing DSL & vanilla steps
    
    Feature: Using the vanilla steps
        Given pet '1234' does not exist
        When the 'Find pet by ID' operation is called with path params:
            | petId | 1234 |
        Then the response status will be '404'
        And the content will contain values:
            | error.code | 0x234 |

    Feature: Wrapping in a custom DSL
        Given pet '1234' does not exist
        When the 'Find pet by ID' operation is called with id '1234'
        Then the response status will be '404'
        And the error code will be '0x234'

type DSL struct {
    c *cucumboa.Context
}

func (dsl *DSL) CallOperationWithIdPathParamStep(operation string, id string) error {
    dsl.c.SetOperation(operation)
    dsl.c.SetPathParams(map[string]string{
        petId: id
    })
    return nil
}

func (dsl *DSL) AssertResponseContentErrorCodeStep(code string) {
    dsl.c.AssertResponseContentContainsValues(map[string]string{
        code: code,
    })

}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertResponseContentContainsValues

func AssertResponseContentContainsValues(ctx *Context, values map[string]string) error

Assert that the response body contains certain values Deep-nested json properties can be referenced using dot notation Properties not passed in the values map are ignored

func LoadOpenApiSchemaFromFile

func LoadOpenApiSchemaFromFile(filePath string) (openapi.Schema, error)

Load an OpenApi schema from a file

func LoadOpenApiSchemaFromUrl

func LoadOpenApiSchemaFromUrl(uri string) (openapi.Schema, error)

Load an OpenApi schema from a URL

func MustLoadOpenApiSchemaFromFile

func MustLoadOpenApiSchemaFromFile(filePath string) openapi.Schema

Load an OpenApi schema from a file, or panic if something goes wrong

func MustLoadOpenApiSchemaFromUrl

func MustLoadOpenApiSchemaFromUrl(uri string) openapi.Schema

Load an OpenApi schema from a URL, or panic if something goes wrong

func RunSimpleTestSuite

func RunSimpleTestSuite(t *testing.T, initScenario func(ctx *godog.ScenarioContext), features []string)

func ValidateResponseBody

func ValidateResponseBody(ctx *Context) error

Validates a response against the OpenAPI schema

Types

type Context

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

Context struct is the main state manager that you interact with

func InitializeScenario

func InitializeScenario(scenario *godog.ScenarioContext, opts Options) (*Context, error)

Initialize a godog scenario with a default context. Returns the context object so you can use it for extended DSLs

func InitializeScenarioWithContext

func InitializeScenarioWithContext(scenario *godog.ScenarioContext, ctx *Context) (*Context, error)

Initialzie a godog scenario by passing in your own context Returns the context object so you can use it for extended DSLs

func (*Context) GetResponse

func (c *Context) GetResponse() *http.Response

Returns the http response The request will be triggerred if it has not already been sent

func (*Context) GetResponseBody

func (c *Context) GetResponseBody() []byte

Returns the http response body The request will be triggerred if it has not already been sent

func (*Context) SetOperation

func (c *Context) SetOperation(operationId string) error

Specifies the operation to be called

func (*Context) SetPathParams

func (c *Context) SetPathParams(params map[string]string) error

Specifies the path params to use when constructing the operation url path

type Dispatcher

type Dispatcher interface {
	Dispatch(request *http.Request) (*http.Response, error)
}

The Dispatcher interface provides a way for cucumboa to call OpenApi operations on a target system

func CreateHandlerDispatcher

func CreateHandlerDispatcher(handler http.Handler) Dispatcher

Craetes a cucumboa dispatcher that runs against a local http.Handler instance to allow testing in-memory without needing an http server

type Options

type Options struct {
	// Path or URL to OpenApi schema for this API
	Schema openapi.Schema
	// Http dispatcher to handle sending of HTTP requests to your API
	Dispatcher Dispatcher
}

Directories

Path Synopsis
examples
internal

Jump to

Keyboard shortcuts

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