From 452fa6e0c3be29c3410e1013e05df242ae474d89 Mon Sep 17 00:00:00 2001 From: Jesus Farfan Date: Fri, 30 Aug 2024 13:13:08 -0300 Subject: [PATCH] feat(mocks): added support for reusing mocks any times (#154) * feat(mocks): added support for reusing mocks any times * feat(mocks): min fix on assert unmatches mocks, and update documentation --- README.md | 14 ++++++++++++++ apitest.go | 7 ++++++- apitest_test.go | 26 ++++++++++++++++++++++++++ mocks.go | 11 +++++++++-- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f5b50dc..06e5a06 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/apitest.go b/apitest.go index 3a49929..77f2991 100644 --- a/apitest.go +++ b/apitest.go @@ -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() @@ -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}) } } diff --git a/apitest_test.go b/apitest_test.go index 7728ad7..8f9822b 100644 --- a/apitest_test.go +++ b/apitest_test.go @@ -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 } diff --git a/mocks.go b/mocks.go index 73293a2..bf97bbd 100644 --- a/mocks.go +++ b/mocks.go @@ -227,6 +227,7 @@ type Mock struct { debugStandalone bool times int timesSet bool + anyTimesSet bool } // Matches checks whether the given request matches the mock @@ -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 } @@ -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() @@ -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