Skip to content

Commit

Permalink
feat(mocks): added support for reusing mocks any times (#154)
Browse files Browse the repository at this point in the history
* feat(mocks): added support for reusing mocks any times

* feat(mocks): min fix on assert unmatches mocks, and update documentation
  • Loading branch information
nomadesoft authored Aug 30, 2024
1 parent 913343f commit 452fa6e
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 3 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,20 @@ func TestApi(t *testing.T) {
End()
}
```
It is possible to configure the mock for using `AnyTimes` feature, it allows a mock to be invoked any number of times
without failing the asserts if it is not used the expected number of times.

This is very useful in scenarios where the exact number of invocations is not known or not important.

```go
var getUser := apitest.NewMock().
Get("http://localhost:8080").
RespondWith().
Status(http.StatusOK).
AnyTimes().
End()
```
Note: The `AnyTimes` method can be combined with other methods such as `Times`, but if `AnyTimes` is set, the `Times` setting will have no effect.

#### Generating sequence diagrams from tests

Expand Down
7 changes: 6 additions & 1 deletion apitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ func (a *APITest) HandlerFunc(handlerFunc http.HandlerFunc) *APITest {
func (a *APITest) Mocks(mocks ...*Mock) *APITest {
var m []*Mock
for i := range mocks {
if mocks[i].anyTimesSet {
m = append(m, mocks[i])
continue
}

times := mocks[i].response.mock.times
for j := 1; j <= times; j++ {
mockCpy := mocks[i].copy()
Expand Down Expand Up @@ -895,7 +900,7 @@ func (r *Response) runTest() *http.Response {

func (a *APITest) assertMocks() {
for _, mock := range a.mocks {
if mock.isUsed == false && mock.timesSet {
if mock.anyTimesSet == false && mock.isUsed == false && mock.timesSet {
a.verifier.Fail(a.t, "mock was not invoked expected times", failureMessageArgs{Name: a.name})
}
}
Expand Down
26 changes: 26 additions & 0 deletions apitest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,32 @@ func TestApiTest_MatchesTimes(t *testing.T) {
assert.Equal(t, 0, len(res.UnmatchedMocks()))
}

func TestApiTest_MatchesAnyTimes(t *testing.T) {
getUser := apitest.NewMock().
Get("http://localhost:8080").
RespondWith().
Status(http.StatusOK).
Times(2).
AnyTimes().
End()

res := apitest.New().
Mocks(getUser).
Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Call getUserData() many times
_ = getUserData()
_ = getUserData()
_ = getUserData()
w.WriteHeader(http.StatusOK)
})).
Get("/").
Expect(t).
Status(http.StatusOK).
End()

assert.Equal(t, 0, len(res.UnmatchedMocks()))
}

type RecorderCaptor struct {
capturedRecorder apitest.Recorder
}
Expand Down
11 changes: 9 additions & 2 deletions mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ type Mock struct {
debugStandalone bool
times int
timesSet bool
anyTimesSet bool
}

// Matches checks whether the given request matches the mock
Expand Down Expand Up @@ -454,7 +455,7 @@ func matches(req *http.Request, mocks []*Mock) (*MockResponse, error) {
mockError := newUnmatchedMockError()
for mockNumber, mock := range mocks {
mock.m.Lock() // lock is for isUsed when matches is called concurrently by RoundTripper
if mock.isUsed {
if mock.isUsed && mock.anyTimesSet == false {
mock.m.Unlock()
continue
}
Expand Down Expand Up @@ -736,7 +737,7 @@ func (r *MockResponse) FixedDelay(delay int64) *MockResponse {
return r
}

// Times respond the given number of times
// Times respond the given number of times, if AnyTimes is set this has no effect
func (r *MockResponse) Times(times int) *MockResponse {
r.mu.Lock()
defer r.mu.Unlock()
Expand All @@ -746,6 +747,12 @@ func (r *MockResponse) Times(times int) *MockResponse {
return r
}

// AnyTimes respond any number of times
func (r *MockResponse) AnyTimes() *MockResponse {
r.mock.anyTimesSet = true
return r
}

// End finalise the response definition phase in order for the mock to be used
func (r *MockResponse) End() *Mock {
return r.mock
Expand Down

0 comments on commit 452fa6e

Please sign in to comment.