Categorygithub.com/jarcoal/httpmock
modulepackage
1.3.1
Repository: https://github.com/jarcoal/httpmock.git
Documentation: pkg.go.dev

# README

httpmock Build Status Coverage Status GoDoc Version Mentioned in Awesome Go

Easy mocking of http responses from external resources.

Install

Currently supports Go 1.13 to 1.21 and is regularly tested against tip.

v1 branch has to be used instead of master.

In your go files, simply use:

import "github.com/jarcoal/httpmock"

Then next go mod tidy or go test invocation will automatically populate your go.mod with the latest httpmock release, now Version.

Usage

Simple Example:

func TestFetchArticles(t *testing.T) {
  httpmock.Activate()
  defer httpmock.DeactivateAndReset()

  // Exact URL match
  httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
    httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))

  // Regexp match (could use httpmock.RegisterRegexpResponder instead)
  httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`,
    httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`))

  // do stuff that makes a request to articles
  ...

  // get count info
  httpmock.GetTotalCallCount()

  // get the amount of calls for the registered responder
  info := httpmock.GetCallCountInfo()
  info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles
  info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12
  info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/<any-number>
}

Advanced Example:

func TestFetchArticles(t *testing.T) {
  httpmock.Activate()
  defer httpmock.DeactivateAndReset()

  // our database of articles
  articles := make([]map[string]interface{}, 0)

  // mock to list out the articles
  httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
    func(req *http.Request) (*http.Response, error) {
      resp, err := httpmock.NewJsonResponse(200, articles)
      if err != nil {
        return httpmock.NewStringResponse(500, ""), nil
      }
      return resp, nil
    })

  // return an article related to the request with the help of regexp submatch (\d+)
  httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`,
    func(req *http.Request) (*http.Response, error) {
      // Get ID from request
      id := httpmock.MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch
      return httpmock.NewJsonResponse(200, map[string]interface{}{
        "id":   id,
        "name": "My Great Article",
      })
    })

  // mock to add a new article
  httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles",
    func(req *http.Request) (*http.Response, error) {
      article := make(map[string]interface{})
      if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
        return httpmock.NewStringResponse(400, ""), nil
      }

      articles = append(articles, article)

      resp, err := httpmock.NewJsonResponse(200, article)
      if err != nil {
        return httpmock.NewStringResponse(500, ""), nil
      }
      return resp, nil
    })

  // mock to add a specific article, send a Bad Request response
  // when the request body contains `"type":"toy"`
  httpmock.RegisterMatcherResponder("POST", "https://api.mybiz.com/articles",
    httpmock.BodyContainsString(`"type":"toy"`),
    httpmock.NewStringResponder(400, `{"reason":"Invalid article type"}`))

  // do stuff that adds and checks articles
}

Algorithm

When GET http://example.tld/some/path?b=12&a=foo&a=bar request is caught, all standard responders are checked against the following URL or paths, the first match stops the search:

  1. http://example.tld/some/path?b=12&a=foo&a=bar (original URL)
  2. http://example.tld/some/path?a=bar&a=foo&b=12 (sorted query params)
  3. http://example.tld/some/path (without query params)
  4. /some/path?b=12&a=foo&a=bar (original URL without scheme and host)
  5. /some/path?a=bar&a=foo&b=12 (same, but sorted query params)
  6. /some/path (path only)

If no standard responder matched, the regexp responders are checked, in the same order, the first match stops the search.

go-testdeep + tdsuite example:

// article_test.go

import (
  "testing"

  "github.com/jarcoal/httpmock"
  "github.com/maxatome/go-testdeep/helpers/tdsuite"
  "github.com/maxatome/go-testdeep/td"
)

type MySuite struct{}

func (s *MySuite) Setup(t *td.T) error {
  // block all HTTP requests
  httpmock.Activate()
  return nil
}

func (s *MySuite) PostTest(t *td.T, testName string) error {
  // remove any mocks after each test
  httpmock.Reset()
  return nil
}

func (s *MySuite) Destroy(t *td.T) error {
  httpmock.DeactivateAndReset()
  return nil
}

func TestMySuite(t *testing.T) {
  tdsuite.Run(t, &MySuite{})
}

func (s *MySuite) TestArticles(assert, require *td.T) {
  httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
    httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))

  // do stuff that makes a request to articles.json
}

Ginkgo example:

// article_suite_test.go

import (
  // ...
  "github.com/jarcoal/httpmock"
)
// ...
var _ = BeforeSuite(func() {
  // block all HTTP requests
  httpmock.Activate()
})

var _ = BeforeEach(func() {
  // remove any mocks
  httpmock.Reset()
})

var _ = AfterSuite(func() {
  httpmock.DeactivateAndReset()
})


// article_test.go

import (
  // ...
  "github.com/jarcoal/httpmock"
)

var _ = Describe("Articles", func() {
  It("returns a list of articles", func() {
    httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
      httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))

    // do stuff that makes a request to articles.json
  })
})

Ginkgo + Resty Example:

// article_suite_test.go

import (
  // ...
  "github.com/jarcoal/httpmock"
  "github.com/go-resty/resty"
)
// ...
var _ = BeforeSuite(func() {
  // block all HTTP requests
  httpmock.ActivateNonDefault(resty.DefaultClient.GetClient())
})

var _ = BeforeEach(func() {
  // remove any mocks
  httpmock.Reset()
})

var _ = AfterSuite(func() {
  httpmock.DeactivateAndReset()
})


// article_test.go

import (
  // ...
  "github.com/jarcoal/httpmock"
  "github.com/go-resty/resty"
)

var _ = Describe("Articles", func() {
  It("returns a list of articles", func() {
    fixture := `{"status":{"message": "Your message", "code": 200}}`
    responder := httpmock.NewStringResponder(200, fixture)
    fakeUrl := "https://api.mybiz.com/articles.json"
    httpmock.RegisterResponder("GET", fakeUrl, responder)

    // fetch the article into struct
    articleObject := &models.Article{}
    _, err := resty.R().SetResult(articleObject).Get(fakeUrl)

    // do stuff with the article object ...
  })
})

# Functions

Activate starts the mock environment.
ActivateNonDefault starts the mock environment with a non-default [*http.Client].
BodyContainsBytes returns a [Matcher] checking that request body contains subslice.
BodyContainsString returns a [Matcher] checking that request body contains substr.
ConnectionFailure is a responder that returns a connection failure.
Deactivate shuts down the mock environment.
DeactivateAndReset is just a convenience method for calling [Deactivate] and then [Reset].
Disabled allows to test whether httpmock is enabled or not.
GetCallCountInfo gets the info on all the calls httpmock has caught since it was activated or reset.
GetSubmatch has to be used in Responders installed by [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix (as well as [MockTransport.RegisterRegexpResponder] or [MockTransport.RegisterResponder]).
GetSubmatchAsFloat has to be used in Responders installed by [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix (as well as [MockTransport.RegisterRegexpResponder] or [MockTransport.RegisterResponder]).
GetSubmatchAsInt has to be used in Responders installed by [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix (as well as [MockTransport.RegisterRegexpResponder] or [MockTransport.RegisterResponder]).
GetSubmatchAsUint has to be used in Responders installed by [RegisterRegexpResponder] or [RegisterResponder] + "=~" URL prefix (as well as [MockTransport.RegisterRegexpResponder] or [MockTransport.RegisterResponder]).
GetTotalCallCount gets the total number of calls httpmock has taken since it was activated or reset.
HeaderContains returns a [Matcher] checking that request contains key header itself containing substr.
HeaderExists returns a [Matcher] checking that request contains key header.
HeaderIs returns a [Matcher] checking that request contains key header set to value.
IgnoreMatcherHelper should be called by external helpers building [Matcher], typically in an init() function, to avoid they appear in the autogenerated [Matcher] names.
MustGetSubmatch works as [GetSubmatch] except that it panics in case of error (submatch not found).
MustGetSubmatchAsFloat works as [GetSubmatchAsFloat] except that it panics in case of error (submatch not found or invalid float64 format).
MustGetSubmatchAsInt works as [GetSubmatchAsInt] except that it panics in case of error (submatch not found or invalid int64 format).
MustGetSubmatchAsUint works as [GetSubmatchAsUint] except that it panics in case of error (submatch not found or invalid uint64 format).
NewBytesResponder creates a [Responder] from a given body (as a byte slice) and status code.
NewBytesResponse creates an [*http.Response] with a body based on the given bytes.
NewErrorResponder creates a [Responder] that returns an empty request and the given error.
NewJsonResponder creates a [Responder] from a given body (as an any that is encoded to JSON) and status code.
NewJsonResponderOrPanic is like [NewJsonResponder] but panics in case of error.
NewJsonResponse creates an [*http.Response] with a body that is a JSON encoded representation of the given any.
NewMatcher returns a [Matcher].
NewMockTransport creates a new [*MockTransport] with no responders.
NewNotFoundResponder creates a [Responder] typically used in conjunction with [RegisterNoResponder] function and [testing] package, to be proactive when a [Responder] is not found.
NewRespBodyFromBytes creates an [io.ReadCloser] from a byte slice that is suitable for use as an HTTP response body.
NewRespBodyFromString creates an [io.ReadCloser] from a string that is suitable for use as an HTTP response body.
NewStringResponder creates a [Responder] from a given body (as a string) and status code.
NewStringResponse creates an [*http.Response] with a body based on the given string.
NewXmlResponder creates a [Responder] from a given body (as an any that is encoded to XML) and status code.
NewXmlResponderOrPanic is like [NewXmlResponder] but panics in case of error.
NewXmlResponse creates an [*http.Response] with a body that is an XML encoded representation of the given any.
RegisterMatcherResponder adds a new responder, associated with a given HTTP method, URL (or path) and [Matcher].
RegisterMatcherResponderWithQuery is same as [RegisterMatcherResponder], but it doesn't depend on query items order.
RegisterNoResponder is used to register a responder that is called if no other responders are found.
RegisterRegexpMatcherResponder adds a new responder, associated with a given HTTP method, URL (or path) regular expression and [Matcher].
RegisterRegexpResponder adds a new responder, associated with a given HTTP method and URL (or path) regular expression.
RegisterResponder adds a new responder, associated with a given HTTP method and URL (or path).
RegisterResponderWithQuery it is same as [RegisterResponder], but doesn't depends on query items order.
Reset removes any registered mocks and returns the mock environment to its initial state.
ResponderFromMultipleResponses wraps an [*http.Response] list in a [Responder].
ResponderFromResponse wraps an [*http.Response] in a [Responder].
ZeroCallCounters zeroes call counters without touching registered responders.

# Variables

DefaultTransport is the default mock transport used by [Activate], [Deactivate], [Reset], [DeactivateAndReset], [RegisterResponder], [RegisterRegexpResponder], [RegisterResponderWithQuery] and [RegisterNoResponder].
ErrSubmatchNotFound is the error returned by GetSubmatch* functions when the given submatch index cannot be found.
InitialTransport is a cache of the original transport used so we can put it back when [Deactivate] is called.
NoResponderFound is returned when no responders are found for a given HTTP method and URL.

# Structs

Matcher type defines a match case.
MockTransport implements [http.RoundTripper] interface, which fulfills single HTTP requests issued by an [http.Client].

# Type aliases

File is a file name.
MatcherFunc type is the function to use to check a [Matcher] matches an incoming request.
Responder is a callback that receives an [*http.Request] and returns a mocked response.