testify
testify copied to clipboard
Negative expectations
The the moment once cannot express negative expectations via .On()
. Instead, you must use .AssertNotCalled
. It would be useful if negative expectations could be expressed something like .On().Never()
. Note that .On().Times(0)
does not work.
We could use AssertNotCalled
. Is there any other specific reason or thoughts that you want such feature. Times(0)
doesn't work, because closest call checks for -1.
if call.Method == method && call.Repeatability > -1 {
we could make the Times panic or fail if its passed with an argument <= 0
Even if we set expectation like mock.Never("method")
before the actual call of function and it panics during actual call, I still feel that both would be the same feature wise.
@eliaslevy I wish Times(0)
worked as you would expect. Unfortunately, it is a breaking change that requires a new major version for testify.
You can do Times(-1)
.
I wouldn't be opposed to adding .Never()
which sets Repeatability
to -1
.
I don't think Never()
would be very useful, it's almost the same as not defining a call at all.
Right now in v1.6.1 Times(-1)
does not work, it fails expectations whether the call is made or not:
package kata
import (
"testing"
"github.com/stretchr/testify/mock"
)
type M struct {
mock.Mock
}
func (m *M) Do() {
m.Called()
}
func TestMockNotCalled(t *testing.T) {
m := &M{}
m.Test(t)
defer m.AssertExpectations(t)
m.On("Do").Return().Times(-1)
}
func TestMockCalled(t *testing.T) {
m := &M{}
m.Test(t)
defer m.AssertExpectations(t)
m.On("Do").Return().Times(-1)
m.Do()
}
Running tool: /usr/local/go/bin/go test -timeout 30s -coverprofile=/var/folders/8x/rpf58y2d68z45w0b5_mg47b00000gn/T/vscode-goCmRNOx/go-code-cover github.com/brackendawson/kata
--- FAIL: TestMockNotCalled (0.00s)
kata_test.go:22: FAIL: Do()
at: [kata_test.go:21]
kata_test.go:22: FAIL: 0 out of 1 expectation(s) were met.
The code you are testing needs to make 1 more call(s).
at: [kata_test.go:22]
--- FAIL: TestMockCalled (0.00s)
mock.go:256:
assert: mock: The method has been called over 0 times.
Either do one more Mock.On("Do").Return(...), or remove extra call.
This call was unexpected:
Do()
at: [kata_test.go:14 kata_test.go:29]
panic.go:617: FAIL: Do()
at: [kata_test.go:28]
panic.go:617: FAIL: 0 out of 1 expectation(s) were met.
The code you are testing needs to make 1 more call(s).
at: [panic.go:617 testing.go:657 kata_test.go:14 kata_test.go:29]
FAIL
coverage: [no statements]
FAIL github.com/brackendawson/kata 0.198s
FAIL
My preference would be that Times(0)
just do what you expect.
I wanted to add a possible use case here. In a table driven test, you might want to provide the number of times you expect a mocked method to be called. Sometimes, you may want a certain mocked method to not be called. In this case, you have to add an if statement in you test to check if the expected number of calls is >0, is so, setup your call assertions. A novel example:
package metricsmiddleware_test
import (
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/mymodule/mocks"
)
func TestMetricsMiddleware(t *testing.T) {
table := []struct {
name string
status int
expectedLatency time.Duration
expected4XXCalls int
expected5XXCalls int
}{
{
name: "it should send a latency metric with the expected duration",
status: 200,
expectedLatency: time.Millisecond,
expected4XXCalls: 0,
expected5XXCalls: 0,
},
{
name: "it should send a 4xx error metric when the response status code is in the 400 range",
status: 422,
expectedLatency: time.Millisecond,
expected4XXCalls: 1,
expected5XXCalls: 0,
},
{
name: "it should send a 5xx error metric when the response status code is in the 500 range",
status: 500,
expectedLatency: time.Millisecond,
expected4XXCalls: 1,
expected5XXCalls: 0,
},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
mockMetrics := &mocks.Metrics{}
mockMetrics.On("Timing", "latency", test.expectedLatency, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
mockMetrics.On("Incr", "error_4xx", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Times(test.expected4XXCalls)
mockMetrics.On("Incr", "error_5xx", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Times(test.expected5XXCalls)
mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(test.status)
})
mw := middleware.NewMetricsMiddleware(mockMetrics)(mockHandler)
mw.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/foo", nil))
mockMetrics.AssertExpectations(t)
})
}
}
The first test in the table won't pass with the current state, as 0 is passed to Times()
.