Skip to content

Commit

Permalink
Merge pull request #136 from jarcoal/matchers
Browse files Browse the repository at this point in the history
feat: introduce new matchers feature
  • Loading branch information
maxatome authored Nov 23, 2022
2 parents f6c4876 + f69cd5e commit b9e83a2
Show file tree
Hide file tree
Showing 10 changed files with 2,005 additions and 266 deletions.
15 changes: 3 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, tip]
go-version: [1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, tip]
full-tests: [false]
include:
- go-version: 1.19.x
Expand All @@ -31,7 +31,7 @@ jobs:
if: matrix.full-tests
run: |
curl -sL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh |
sh -s -- -b $HOME/go/bin v1.49.0
sh -s -- -b $HOME/go/bin v1.50.1
$HOME/go/bin/golangci-lint run --max-issues-per-linter 0 \
--max-same-issues 0 \
-E bidichk \
Expand All @@ -54,15 +54,6 @@ jobs:
if [ ${{ matrix.full-tests }} = true ]; then
GO_TEST_OPTS="-covermode=atomic -coverprofile=coverage.out"
fi
case ${{ matrix.go-version }} in
1.9.x | 1.10.x) # Before go 1.11, go modules are not available
mkdir -p ../src/github.com/$GITHUB_REPOSITORY_OWNER
ln -s $(pwd) ../src/github.com/$GITHUB_REPOSITORY
export GOPATH=$(dirname $(pwd))
cd $GOPATH/src/github.com/$GITHUB_REPOSITORY
go get -t ./...
;;
esac
export GORACE="halt_on_error=1"
go test -race $GO_TEST_OPTS ./...
Expand All @@ -71,5 +62,5 @@ jobs:
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go install github.com/mattn/goveralls@v0.0.9
go install github.com/mattn/goveralls@v0.0.11
goveralls -coverprofile=coverage.out -service=github
61 changes: 11 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,19 @@ Easy mocking of http responses from external resources.

## Install

Currently supports Go 1.9 - 1.19.
Currently supports Go 1.13 to 1.19 and is regularly tested against tip.

`v1` branch has to be used instead of `master`.


### Using go modules (aka. `go mod`)

In your go files, simply use:
```go
import "github.com/jarcoal/httpmock"
```

Then next `go mod tidy` or `go test` invocation will automatically
populate your `go.mod` with the last httpmock release, now
populate your `go.mod` with the latest httpmock release, now
[![Version](https://img.shields.io/github/tag/jarcoal/httpmock.svg)](https://github.com/jarcoal/httpmock/releases).

Note you can use `go mod vendor` to vendor your dependencies.


### Using `$GOPATH`

`v1` branch is configured as the default branch in github, so:
```
go get github.com/jarcoal/httpmock
```

automatically downloads the `v1` branch in `$GOPATH/src`. Then in your
go files use:
```go
import "github.com/jarcoal/httpmock"
```


### Vendoring, using [`govendor`](https://github.com/kardianos/govendor) for example

When vendoring is used, `v1` branch has to be specified. Two choices here:

- preferred way:
```
govendor fetch github.com/jarcoal/httpmock@v1
```
then in go files:
```go
import "github.com/jarcoal/httpmock"
```
- old way (before `v1` was set as default branch), use gopkg to read from
`v1` branch:
```
govendor fetch gopkg.in/jarcoal/httpmock.v1
```
then in go files:
```go
import "gopkg.in/jarcoal/httpmock.v1"
```


## Usage

Expand Down Expand Up @@ -107,8 +65,7 @@ func TestFetchArticles(t *testing.T) {
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`,
Expand All @@ -119,8 +76,7 @@ func TestFetchArticles(t *testing.T) {
"id": id,
"name": "My Great Article",
})
},
)
})

// mock to add a new article
httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles",
Expand All @@ -137,8 +93,13 @@ func TestFetchArticles(t *testing.T) {
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
}
Expand Down
87 changes: 87 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package httpmock

import (
"io"
"net/http"
"reflect"
"sync/atomic"

"github.com/jarcoal/httpmock/internal"
)

var (
GetPackage = getPackage
ExtractPackage = extractPackage
CalledFrom = calledFrom
)

type (
MatchResponder = matchResponder
MatchResponders = matchResponders
)

func init() {
atomic.AddInt64(&matcherID, 0xabcdef)
}

func GetIgnorePackages() map[string]bool {
return ignorePackages
}

// bodyCopyOnRead

func NewBodyCopyOnRead(body io.ReadCloser) *bodyCopyOnRead { //nolint: revive
return &bodyCopyOnRead{body: body}
}

func (b *bodyCopyOnRead) Body() io.ReadCloser {
return b.body
}

func (b *bodyCopyOnRead) Buf() []byte {
return b.buf
}

func (b *bodyCopyOnRead) Rearm() {
b.rearm()
}

// matchRouteKey

func NewMatchRouteKey(rk internal.RouteKey, name string) matchRouteKey { //nolint: revive
return matchRouteKey{RouteKey: rk, name: name}
}

// matchResponder

func NewMatchResponder(matcher Matcher, resp Responder) matchResponder { //nolint: revive
return matchResponder{matcher: matcher, responder: resp}
}

func (mr matchResponder) ResponderPointer() uintptr {
return reflect.ValueOf(mr.responder).Pointer()
}

func (mr matchResponder) Matcher() Matcher {
return mr.matcher
}

// matchResponders

func (mrs matchResponders) Add(mr matchResponder) matchResponders {
return mrs.add(mr)
}

func (mrs matchResponders) Remove(name string) matchResponders {
return mrs.remove(name)
}

func (mrs matchResponders) FindMatchResponder(req *http.Request) *matchResponder {
return mrs.findMatchResponder(req)
}

// Matcher

func (m Matcher) FnPointer() uintptr {
return reflect.ValueOf(m.fn).Pointer()
}
8 changes: 7 additions & 1 deletion internal/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ var NoResponderFound = errors.New("no responder found") // nolint: revive
// ErrorNoResponderFoundMistake encapsulates a NoResponderFound
// error probably due to a user error on the method or URL path.
type ErrorNoResponderFoundMistake struct {
Kind string // "method" or "URL"
Kind string // "method", "URL" or "matcher"
Orig string // original wrong method/URL, without any matching responder
Suggested string // suggested method/URL with a matching responder
}
Expand All @@ -26,6 +26,12 @@ func (e *ErrorNoResponderFoundMistake) Unwrap() error {

// Error implements error interface.
func (e *ErrorNoResponderFoundMistake) Error() string {
if e.Kind == "matcher" {
return fmt.Sprintf("%s despite %s",
NoResponderFound,
e.Suggested,
)
}
return fmt.Sprintf("%[1]s for %[2]s %[3]q, but one matches %[2]s %[4]q",
NoResponderFound,
e.Kind,
Expand Down
8 changes: 7 additions & 1 deletion internal/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ func TestErrorNoResponderFoundMistake(t *testing.T) {
Orig: "pipo",
Suggested: "BINGO",
}

td.Cmp(t, e.Error(), `no responder found for method "pipo", but one matches method "BINGO"`)
td.Cmp(t, e.Unwrap(), internal.NoResponderFound)

e = &internal.ErrorNoResponderFoundMistake{
Kind: "matcher",
Orig: "--not--used--",
Suggested: "BINGO",
}
td.Cmp(t, e.Error(), `no responder found despite BINGO`)
td.Cmp(t, e.Unwrap(), internal.NoResponderFound)
}
Loading

0 comments on commit b9e83a2

Please sign in to comment.