-
Notifications
You must be signed in to change notification settings - Fork 606
panic in test is swallowed if there are failed expectations and t.Cleanup(ctrl.Finish) #428
Comments
Thanks for the feedback, I agree I think we could do more for the cases where the mocks are not quite setup right. I think there are other related issues here, will link when I re-come across them. |
I just ran into this issue today as well. This only happens from Go 1.14 and forward when an explicit defer ctrl.Finish() is no longer expected because it is registered through the testing.T interface. If one explicitly does a defer ctrl.Finish() then the panics are not swallowed. |
This is still quite annoying - test just hangs without any output. @codyoss If you can provide some quick hints about how to implement possible fix then I can try to prepare PR. In the meanwhile I'd like to share two workarounds:
defer func() {
if err := recover(); err != nil {
t.Error(err)
}
}() |
@powerman I did open up a PR for this yesterday. You can see the link above your comment 😸 I think that should solve the issue. Check out the PR and let me know if it does not though. |
@codyoss Your PR didn't fix the issue. Here is minimal example to reproduce hang: //go:generate mockgen -package=$GOPACKAGE -source=$GOFILE -destination=mock.$GOFILE T
package gomock428
import (
"testing"
"github.com/golang/mock/gomock"
)
type T interface {
F(string) error
}
func Test428(t *testing.T) {
ctrl := gomock.NewController(t)
t.Cleanup(ctrl.Finish)
o := NewMockT(ctrl)
o.EXPECT().F("ok").Return(nil) // <--- replace with o.EXCEPT() to fix the hang
t.Run("", func(t *testing.T) {
panic("boom")
})
} |
A couple things:
|
Sorry, I'm too sleepy now to answer 1 and 3, but this one surprises me:
I never bother about this and used t.Run a lot without any issues. Moreover, having to define mocks inside t.Run will be incredible inconvenient in a very usual case when we define EXPECT before loop over test cases (e.g. https://github.com/powerman/go-service-goswagger-clean-example/blob/46dc447e66267226c4432a448722ead4b7276adc/internal/srv/openapi/handlers_test.go#L23-L36) - because to call EXPECT within t.Run we'll need to include details about how to call EXPECT inside data structure which describes test cases. |
It is an issue if there are errors. Here is an example:
package gm428
//go:generate mockgen -package=gm428 -source=gm428.go -destination=gm428_mock.go
type Foo interface {
Bar() string
}
func Baz(f Foo) string {
return f.Bar()
}
package gm428
import (
"testing"
gomock "github.com/golang/mock/gomock"
)
func TestBaz(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockFoo(ctrl)
t.Run("one", func(t *testing.T) {
//mock.EXPECT().Bar().Return("Bar")
Baz(mock)
})
// This will never run
t.Run("two", func(t *testing.T) {
})
} Run tests === RUN TestBaz
=== RUN TestBaz/one
=== CONT TestBaz
gm428.go:10: Unexpected call to *gm428.MockFoo.Bar([]) at /tmp/gm428_mock.go:39 because: there are no expected calls of the method "Bar" for that receiver
=== CONT TestBaz/one
testing.go:1033: test executed panic(nil) or runtime.Goexit: subtest may have called FailNow on a parent test
--- FAIL: TestBaz (0.00s)
--- FAIL: TestBaz/one (0.00s)
FAIL
exit status 1
FAIL example.com/gm428 0.189s |
I don't see how t.Run affect your example:
If I get you right and you wanna continue test to second t.Run even if mock fails in first t.Run your example should be changed to include all mock-related setup inside each t.Run - this works, but it looks overkill and not needed in most cases to me: func TestBaz(t *testing.T) {
t.Run("one", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockFoo(ctrl)
// mock.EXPECT().Bar().Return("Bar")
Baz(mock)
})
// This will run too
t.Run("two", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockFoo(ctrl)
Baz(mock)
})
} |
This is a cool feature.
Hmm… I see what you mean, but:
I mean, this is what this issue about (but we should update the issue title to mention it happens only with |
There are many cases where gomock will "work", but because the The new If for some reason we decided to support |
As of right now we don't plan to support calling |
I agree that in general case we need to create controller inside t.Run. But! t.Run is often used not to run test at different point in time (e.g. because of t.Parallel inside t.Run), but just to name subtests executed in a loop on table test cases, within normal test flow - as recommended by https://blog.golang.org/subtests. In this case t.Run never contains full setup both because this is done before loop on test cases and because it's impossible to write test this way without injecting large anonymous "init" functions into table with test cases itself (which hurt readability of table tests a lot and thus should be avoid). To me it looks like it's worth to support this use case. As for t.Cleanup vs defer - sure, to catch and correctly handle panic we must use defer… or testing package's implementation of Cleanup must ensure it'll run in case of panic by using defer inside testing package. But this isn't the point - I can live with swallowed messages from gomock about missing calls if I prefer Cleanup to defer to have less boilerplate code in tests. The point is using Cleanup shouldn't result in hang or swallowed panic message. |
@powerman I agree that for a canonical, table-driven golang test there is some unpleasant overhead with gomock. I typically do what you mentioned and use an anonymous function as my table case EG
This could definitely be improved and we are happy for suggestions, as long as they are not breaking changes and don't require the use of the |
More details and possible fix is in golang/go#41355 (comment) |
Actual behavior A clear and concise description of what the bug is.
If test function setup some
EXPECT()
which wasn't called yet and then itpanic()
, the panic message won't be shown in test output, all I get ismissing call(s) to … aborting test due to missing call(s)
message.Expected behavior A clear and concise description of what you expected to happen.
Panic has to be shown in test output. Mentioned above message about missing calls may or may not be shown, it's much less important than panic and often implicit because of panic.
To Reproduce Steps to reproduce the behavior
I can provide trivial example if needed, but I believe it's obvious.
Additional Information
Triage Notes for the Maintainers
The text was updated successfully, but these errors were encountered: