-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Description
Add a spy construct to testify.
Spy Definition from Java https://www.baeldung.com/mockito-spy
When Mockito creates a mock, it does so from the Class of a Type, not from an actual instance.
The mock simply creates a bare-bones shell instance of the Class, entirely instrumented to track interactions with it.On the other hand, the spy will wrap an existing instance. It will still behave in the same way as the normal instance;
the only difference is that it will also be instrumented to track all the interactions with it.
-
A spy is intended to track interactions with an instance, not change its underlying behavior. This lets you assert the invocation of an instance where there may be no result to assert against.
-
Additionally, Mockito's spy allows you to mock the behavior of a function on the spy if needed. This can be used when you don't want to mock every entry point to the instance, only change the behavior of a single point, e.g., returning an error.
Use case
Example use of #1:
func foo(value string, t SomeInterface) {
if t.Check(value) {
t.doSomething()
} else {
t.doSomethingElse()
}
}
This example is contrived, and I could create a dummy interface, but it would be convenient to have
spy := NewSpyFs(t, &SomeImplementation)
spy.EXPECT().
doSomething()
foo("bar", spy)
Versus testing against.
spy := NewSpyFs(t, &SomeImplementation)
spy.EXPECT().
doSomethingElse()
foo("foo", spy)
Example use of #2:
Using afero.Fs, we have a large contract.
type Fs interface {
// Create creates a file in the filesystem, returning the file and an
// error, if any happens.
Create(name string) (File, error)
// Mkdir creates a directory in the filesystem, return an error if any
// happens.
Mkdir(name string, perm os.FileMode) error
// MkdirAll creates a directory path and all parents that does not exist
// yet.
MkdirAll(path string, perm os.FileMode) error
... and more
}
I wanted to test different error-handling scenarios and would like to avoid creating my own implementation that wraps a real instance and controls when to return an error.
// Pass the test context plus a real implementation
spy := NewSpyFs(t, &afero.NewMemMapFs())
spy.EXPECT().
Mkdir("foo", 0o600).
Return(errors.New("explode")