mockapi

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2023 License: MPL-2.0 Imports: 9 Imported by: 0

README

mock-http-api PkgGoDev

Go helpers for mocking an HTTP API using stretchr/testify/mock

Library Usage

package mock_test

import (
   "encoding/json"
   "net/http"
   "testing"
   
   mockapi "github.com/mkeeler/mock-http-api"
)

// This test will pass as all the requisite API calls are made.
func TestMyAPI(t *testing.T) {
   m := mockapi.NewMockAPI(t)

   // http.Get will add both of the headers but we don't want to care about them.
   m.SetFilteredHeaders([]string{
      "Accept-Encoding",
      "User-Agent",
   })
      
   // This sets up an expectation that a GET request to /my/endpoint will be made and that it should
   // return a 200 status code with the provided map sent back JSON encoded as the body of the response
   call := m.WithJSONReply(mockapi.NewMockRequest("GET", "/my/endpoint"), 200, map[string]string{
      "foo": "bar",
   })
      
   // This sets the call to be required to happen exactly once
   call.Once()
      
   // This makes the HTTP request to the mock HTTP server. The mock api server exposes a URL()
   // method which will return a string of the form http://<ip>:<port> that you can use to make requests.
   // Typically this bit of code below would be replaced with invocation of some function that uses
   // your API
   resp, err := http.Get(fmt.Sprintf("%s/my/endpoint", m.URL()))
   if err != nil {
      t.Fatalf("Error issuing GET of /my/endpoint: %v", err)
   }

   defer resp.Body.Close()
   dec := json.NewDecoder(resp.Body)

   var output map[string]string
   if err := dec.Decode(&output); err != nil {
      t.Fatalf("Error decoding response: %v", err)
   }
      
   if val, ok := output["foo"]; !ok || val != "bar" {
      t.Fatalf("Didn't get the expected response")
   }
}

Code Generation

The code generator will create a new mock API type with helper methods for all the desired endpoints. These helpers are meant to be more ergonomic to use that the raw mock-http-api module itself.

Installing the code generator
go get github.com/mkeeler/mock-http-api/cmd/mock-api-gen

Note that you may need to run this command with GO111MODULE=on if executing outside of your GOPATH

Using mock-api-gen
mock-api-gen -type MockMyAPI -endpoints ./endpoints.json -pkg myapi -output api.helpers.go

This command will take in the JSON file of endpoints and generate the desired type with helpers for mocking responses to each API. See endpoint options to view the list of available options to configure mocked endpoints.

The format of the endpoints file is:

{
  "Endpoints": {
    "UpdateResource": {
      "Method": "POST",
      "Path": "/resource/%s",
      "PathParameters": ["resourceID"],
      "BodyFormat": "json",
      "ResponseFormat": "json",
      "Headers": true,
      "QueryParams": false
    }
  }
}

Using this as input the following file would be generated:

// Code generated by "mock-expect-gen -type MockAPI -pkg fakeapi -endpoints endpoints.json -output ./api.go"; DO NOT EDIT.

package fakeapi

import (
   "fmt"
   mockapi "github.com/mkeeler/mock-http-api"
)

type MockAPI struct {
   *mockapi.MockAPI
}

func NewMockAPI(t mockapi.TestingT) *MockAPI {
   return &MockAPI{
      MockAPI: mockapi.NewMockAPI(t),
   }
}

func (m *MockConsulAPI) UpdateResource(resourceID string, headers map[string]string, body map[string]interface{}, status int, reply interface{}) *mockapi.MockAPICall {
   req := mockapi.NewMockRequest("POST", fmt.Sprintf("/resource/%s", resourceID)).WithBody(body).WithHeaders(headers)

   return m.WithJSONReply(req, status, reply)
}

Then when you want to use this you would:

func TestFakeAPI(t *testing.T) {
   m := fakeapi.NewMockAPI(t)
   
   // Not necessary when the `t` passed into NewMockAPI supports a Cleanup method. (such as with the Go 1.14 testing.T type)
   defer m.Close()
   
   m.UpdateResource("some-id-here", 
      nil, 
      map[string]interface{"abc", "def"}, 
      200, 
      map[string]interface{"abc", "def", "added": true})
   
   httpServerURL := m.URL()
   
   // do something to cause the HTTP API call to happen here.
   
   // nothing else is necessary. Either the deferred m.Close or the automatic testing cleanup 
   // will assert that the required API calls were made.
}
Endpoint Options

Endpoint options for generating method signatures:

Argument Type Description
Method string The HTTP method for the endpoint.
Path string The path of the endpoint. Include string format verbs to represent path parameters (/v1/resource/%s).
PathParameters []string List of path parameters of the endpoint.
BodyFormat string The format of the body expected for the HTTP request. For example, none, json, string, stream.
BodyType string A string describing the go type for the method signature to include the typed representation of the request body. The default type is map[string]interface{}. Custom types from other packages, like *api.Resource, are supported. This requires the package to be specified in order to be properly imported. See import options for more information.
QueryParams bool This includes the option for mocking API query params in the method signature with the type map[string]string.
Headers bool This includes the option for HTTP headers for the request in the method signature with the type map[string]string.
ResponseFormat string The format of the response body returned: none, json, string, stream, func.
ResponseType string A string describing the go type for the method signature to include the typed representation of the response body. The default type is interface{}. Custom types from other packages, like *api.Resource, are supported. This requires the package to be specified in order to be properly imported. See import options for more information.
Import Options

To include custom types in the method signature for strict type checking, specify the import package in the endpoints file.

{
  "Imports": {
    "api": "github.com/namespace/project/path/api"
  },
  "Endpoints": {
    "ListResource": {
      "Method": "GET",
      "Path": "/resources",
      "ResponseFormat": "json",
      "ResponseType": "[]*api.Resource"
    }
  }
}
Full Usage
Usage of mock-api-gen:
        mock-api-gen [flags] -type <type name> -endpoints <var name> [package]
Flags:
  -endpoints string
        File holding the endpoint configuration. (default "endpoints")
  -output string
        Output file name.
  -pkg string
        Name of the package to generate methods in
  -tag value
        Build tags the generated file should have. This may be specified multiple times.
  -type string
        Method receiver type the mock API helpers should be generated for

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BodyFormat

type BodyFormat string
const (
	BodyFormatNone   BodyFormat = "none"
	BodyFormatJSON   BodyFormat = "json"
	BodyFormatString BodyFormat = "string"
	BodyFormatStream BodyFormat = "stream"
)

type CleanerT

type CleanerT interface {
	TestingT
	Cleanup(func())
}

CleanerT is the interface that may optionally be implemented for Go 1.14 compatibility in addition to generic testing.T compatibility with older versions.

type Endpoint

type Endpoint struct {
	// Path is the HTTP path this endpoint is served under
	Path string
	// Method is the HTTP Method used to invoke this API
	Method string
	// BodyFormat is what format of body to take as input
	BodyFormat BodyFormat
	// BodyType is the golang type of the Body
	BodyType string

	// PathParameters are the parameters required to be in the path
	PathParameters []string
	// ResponseFormat is the format of Response that helpers should
	ResponseFormat ResponseFormat
	// ResponseType is the golang type of the Response
	ResponseType string
	// Headers indicates that this endpoints operation is influenced by
	// headers which may be present and so the headers should be a part
	// of the expectation
	Headers bool
	// QueryParams indicates that this endpoints operation is influenced by
	// query params which may be present and so the params should be part
	// of the expectation
	QueryParams bool
}

Endpoint represents an HTTP endpoint to be mocked. This is mostly used by github.com/mkeeler/mock-http-api/cmd/mock-expect-gen in order to generate expectation helpers for an HTTP API.

type MockAPI

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

MockAPI is the container holding all the bits necessary to provide a mocked HTTP API.

func NewMockAPI

func NewMockAPI(t TestingT) *MockAPI

NewMockAPI creates a MockAPI. If `t` supports the Go 1.14 Cleanup function then a cleanup routine will be setup to close the MockAPI when the test completes. This will teardown the HTTP server and assert that all the required HTTP calls were made. If not using Go 1.14 then the caller should ensure that Close() is called in order to properly shut things down.

func (*MockAPI) AssertExpectations

func (m *MockAPI) AssertExpectations(t TestingT)

AssertExpectations will assert that all expected API invocations have happened and fail the test if any required calls did not happen.

func (*MockAPI) Close

func (m *MockAPI) Close()

Close will stop the HTTP server and also assert that all expected HTTP invocations have happened.

func (*MockAPI) DefaultHandler

func (m *MockAPI) DefaultHandler(response func(http.ResponseWriter, *http.Request)) *MockAPICall

func (*MockAPI) ServeHTTP

func (m *MockAPI) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the HTTP.Handler interface

func (*MockAPI) SetFilteredHeaders

func (m *MockAPI) SetFilteredHeaders(headers []string)

SetFilteredHeaders sets a list of headers that shouldn't be taken into account when recording an API call.

func (*MockAPI) SetFilteredQueryParams

func (m *MockAPI) SetFilteredQueryParams(params []string)

SetFilteredQueryParams sets a list of query params that shouldn't be taken into account when recording an API call.

func (*MockAPI) URL

func (m *MockAPI) URL() string

URL returns the URL the HTTP server is listening on. It will have the form described for the httptest.Server's URL field https://pkg.go.dev/net/http/httptest#Server

func (*MockAPI) WithJSONReply

func (m *MockAPI) WithJSONReply(req *MockRequest, status int, reply interface{}) *MockAPICall

WithJSONReply will setup an expectation for an API call to be made. The supplied status code will be use for the responses reply and the reply object will be JSON encoded and written to the response. If there is an error in JSON encoding it will fail the test object passed into the NewMockAPI constructor if that was non-nil and if it was nil, will panic. The method, path and body parameters are the same as for the Request method.

func (*MockAPI) WithJSONReplyAlter

func (m *MockAPI) WithJSONReplyAlter(req *MockRequest, status int, reply interface{}) (ret *MockAPICall)

func (*MockAPI) WithNoResponseBody

func (m *MockAPI) WithNoResponseBody(req *MockRequest, status int) *MockAPICall

WithNoResponseBody will setup an expectation for an API call to be made. The supplied status code will be used for the responses reply but no response body will be written.

func (*MockAPI) WithRequest

func (m *MockAPI) WithRequest(req *MockRequest, resp MockResponse) *MockAPICall

WithRequest will setup an expectation for an API call to be made. Its is the responsibility of the passed in response function to set the HTTP status code and write out any body. The body may of the MockRequest passed in may be either nil, a []byte or a map[string]interface{}. During processing of the HTTP request, the entire body will be read. If the len is not greater than 0, then nil will be recorded as the body. If the len is greater than 0 an attempt to JSON decode the body contents into a map[string]interface{} is made. If successful the map is recorded as the body, if unsuccessful then the raw []byte is recorded as the body.

func (*MockAPI) WithStreamingReply

func (m *MockAPI) WithStreamingReply(req *MockRequest, status int, reply io.Reader) *MockAPICall

WithStreamingReply will setup an expectation for an API call to be made. The supplied status code will be used for the responses reply and the reply readers content will be copied as the response body.

func (*MockAPI) WithTextReply

func (m *MockAPI) WithTextReply(req *MockRequest, status int, reply string) *MockAPICall

WithTextReply will setup an expectation for an API call to be made. The supplied status code will be use for the responses reply and the reply string will be written to the response.

type MockAPICall

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

MockAPICall is a wrapper around the github.com/stretchr/testify/mock.Call type. It provides a smaller interface that is more suitable for use with the MockAPI type and should prevent some accidental issues.

func (*MockAPICall) Maybe

func (m *MockAPICall) Maybe() *MockAPICall

Maybe marks this API call as optional.

func (*MockAPICall) Once

func (m *MockAPICall) Once() *MockAPICall

Once marks this API call as being expected to occur exactly once.

func (*MockAPICall) Times

func (m *MockAPICall) Times(i int) *MockAPICall

Times marks this API call as being expected to occur the specified number of times.

func (*MockAPICall) Twice

func (m *MockAPICall) Twice() *MockAPICall

Twice marks this API call as being expected to occur exactly twice

func (*MockAPICall) WaitUntil

func (m *MockAPICall) WaitUntil(w <-chan time.Time) *MockAPICall

WaitUntil sets the channel that will block the sending back an HTTP response to this Call. This happens prior to setting the status code as well as writing out any of the reply (before the function passed to MockAPI.Request is called)

type MockRequest

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

MockRequest is the container for all the elements pertaining to an expected API request.

func NewMockRequest

func NewMockRequest(method, path string) *MockRequest

NewMockRequest will create a new MockRequest. Other With* methods can then be called to build out the other parts of the expected request

func (*MockRequest) WithBody

func (r *MockRequest) WithBody(body interface{}) *MockRequest

func (*MockRequest) WithHeaders

func (r *MockRequest) WithHeaders(headers map[string]string) *MockRequest

WithHeaders will set these headers to be expected in the request

func (*MockRequest) WithQueryParams

func (r *MockRequest) WithQueryParams(params map[string]string) *MockRequest

WithQueryParams will set these query params to be expected in the request

type MockResponse

type MockResponse func(http.ResponseWriter, *http.Request)

MockResponse is the type of function that the mock HTTP server is expecting to be used to handle setting up the response. This function should write a status code and maybe a body

type ResponseFormat

type ResponseFormat string
const (
	ResponseFormatJSON   ResponseFormat = "json"
	ResponseFormatString ResponseFormat = "string"
	ResponseFormatStream ResponseFormat = "stream"
	ResponseFormatFunc   ResponseFormat = "func"
)

type TestingT

type TestingT interface {
	mock.TestingT
}

TestingT is the interface encompassing all this libraries unconditional uses of methods typically found on the testing.T type.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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