Skip to content

Create a spy equivalent to mock #1811

@thespags

Description

@thespags

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.

  1. 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.

  2. 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")

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions