disclaimer
the term "reversed assertion" is not a standard terminology in testing—it’s a name coined here to describe the method explained below.
when developing unit tests, you may have encountered patterns similar to this:
func TestIsWeatherReported(t *testing.T) {
mock := &MockWeatherService{}
mock.On("SendWeatherData", mock.Anything).Return(true)
station := NewWeatherStation(mock)
err := station.ReportWeather()
assert.True(t, station.HasReported())
mock.AssertExpectations(t)
}
in the example above, the test verifies the ReportWeather
function of the WeatherStation
struct. the function interacts with the SendWeatherData
method of an external service interface. if the service confirms successful reporting, the HasReported
method should return true
, and mock.AssertExpectations(t)
ensures all mocked interactions meet expectations.
but what happens when we need to test the opposite scenario? for instance, consider a condition where the SendWeatherData
function should not be invoked—such as when an optional argument like DisableReporting
is passed. here's how we can test it:
func TestIsWeatherReported(t *testing.T) {
mock := &MockWeatherService{}
station := NewWeatherStation(mock)
err := station.ReportWeather(DisableReporting())
assert.True(t, station.HasReported())
mock.AssertExpectations(t)
}
in this case, the DisableReporting()
argument prevents the SendWeatherData
function from being called. if mock.AssertExpectations(t)
passes, it confirms the absence of a call since no expectations were set up with On()
.
mocks simplify this process. but what if mocks aren’t used?
alternative case study: testing server interaction
imagine a weather station that reports climate data to an external server for analytics purposes. if the user opts out via a DisableAnalytics
setting, no calls should be made to the server. to test this, we can employ a hermetic (isolated) server.
scenario 1: reporting enabled
when analytics are enabled, the weather station should successfully call the server:
fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "data received"}`))
}))
defer fakeServer.Close()
client := api.NewClient(fakeServer.URL)
station := NewWeatherStation(client)
ok := station.ReportWeather()
assert.True(t, ok)
scenario 2: reporting disabled
now, when analytics are disabled, the weather station must not contact the server. instead of passively asserting the absence of interaction, we fail the test immediately if the server handler is reached:
fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Log("Server should never be called")
t.Fail()
}))
defer fakeServer.Close()
client := api.NewClient(fakeServer.URL)
station := NewWeatherStation(client)
ok := station.ReportWeather(DisableAnalytics()) // Disable reporting
assert.True(t, ok)
here, the server handler explicitly triggers t.Fail()
upon invocation, ensuring no calls are made. this approach provides clarity, as it fails visibly when the server interaction occurs, unlike mocks where failures may remain obscured.
benefits of reversed assertion
compared to using mocks, reversed assertion enhances:
- readability: the failure point is clear within the test code, reducing dependency on external mock behavior.
- immediate feedback: explicit failure occurs when unintended behavior arises, offering faster debugging.
this methodology allows for straightforward, declarative tests, enhancing the maintainability and understanding of your testing suite.
by adopting the reversed assertion technique, tests become more transparent and self-explanatory, particularly in scenarios requiring strict non-interaction validation. while mocks remain a powerful tool, reversed assertions can be a valuable addition to your testing toolbox for specific use cases.