mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 04:51:49 +08:00
Documentation: Add using mock explanation for testing (#46360)
* Add using mock explanation for testing * Update information of how to add go:generate in code Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
@ -58,6 +58,100 @@ code, plus lets you run each test case in isolation when debugging. Don't use `t
|
|||||||
Use [`t.Cleanup`](https://golang.org/pkg/testing/#T.Cleanup) to clean up resources in tests. It's a less fragile choice than `defer`, since it's independent of which
|
Use [`t.Cleanup`](https://golang.org/pkg/testing/#T.Cleanup) to clean up resources in tests. It's a less fragile choice than `defer`, since it's independent of which
|
||||||
function you call it in. It will always execute after the test is over in reverse call order (last `t.Cleanup` first, same as `defer`).
|
function you call it in. It will always execute after the test is over in reverse call order (last `t.Cleanup` first, same as `defer`).
|
||||||
|
|
||||||
|
### Mock
|
||||||
|
|
||||||
|
Optionally, we use [`mock.Mock`](https://github.com/stretchr/testify#mock-package) package to generate mocks. This is
|
||||||
|
useful when you expect different behaviours of the same function.
|
||||||
|
|
||||||
|
#### Tips
|
||||||
|
|
||||||
|
- Use `Once()` or `Times(n)` to make this mock only works `n` times.
|
||||||
|
- Use `mockedClass.AssertExpectations(t)` to guarantee that the mock is called the times asked.
|
||||||
|
- If any mock set is not called or its expects more calls, the test fails.
|
||||||
|
- You can pass `mock.Anything` as argument if you don't care about the argument passed.
|
||||||
|
- Use `mockedClass.AssertNotCalled(t, "FunctionName")` to assert that this test is not called.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
This is an example to easily create a mock of an interface.
|
||||||
|
|
||||||
|
Given this interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func MyInterface interface {
|
||||||
|
Get(ctx context.Context, id string) (Object, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mock implementation should be like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import
|
||||||
|
|
||||||
|
func MockImplementation struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockImplementation) Get(ctx context.Context, id string) error {
|
||||||
|
args := m.Called(ctx, id) // Pass all arguments in order here
|
||||||
|
return args.Get(0).(Object), args.Error(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And use it as the following way:
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
objectToReturn := Object{Message: "abc"}
|
||||||
|
errToReturn := errors.New("my error")
|
||||||
|
|
||||||
|
myMock := &MockImplementation{}
|
||||||
|
defer myMock.AssertExpectations(t)
|
||||||
|
|
||||||
|
myMock.On("Get", mock.Anything, "id1").Return(objectToReturn, errToReturn).Once()
|
||||||
|
myMock.On("Get", mock.Anything, "id2").Return(Object{}, nil).Once()
|
||||||
|
|
||||||
|
anyService := NewService(myMock)
|
||||||
|
resp, err := anyService.Call("id1")
|
||||||
|
|
||||||
|
assert.Equal(t, resp.Message, objectToReturn.Message)
|
||||||
|
assert.Error(t, err, errToReturn)
|
||||||
|
|
||||||
|
resp, err = anyService.Call("id2")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mockery
|
||||||
|
|
||||||
|
When an interface to test is too big, it's annoying to mock each function manually. To avoid this, you can
|
||||||
|
use [`mockery`](https://github.com/vektra/mockery) library to generate the mocks.
|
||||||
|
|
||||||
|
The command is like the following (there are more options documented if you need to use another one):
|
||||||
|
|
||||||
|
```
|
||||||
|
mockery --name InterfaceName --structname MockImplementationName --inpackage --filename my_implementation_mock.go
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--name`: Interface to mock
|
||||||
|
- `--structname`: Mock implementation name
|
||||||
|
- `--inpackage`: To use the same package name as the interface
|
||||||
|
- `--filename`: Your mock generated file name
|
||||||
|
|
||||||
|
If any interface signature changes, executing the command again updates the mock.
|
||||||
|
|
||||||
|
Additionally, you can put `go:generate` command on the top of the file as a comment. It's useful because some IDEs
|
||||||
|
like Goland and Visual Studio Code allows executing scripts from the IDE.
|
||||||
|
|
||||||
|
```
|
||||||
|
package <package>
|
||||||
|
|
||||||
|
import (
|
||||||
|
...
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate mockery --name InterfaceName --structname MockImplementationName --inpackage --filename my_implementation_mock.go
|
||||||
|
```
|
||||||
|
|
||||||
## Globals
|
## Globals
|
||||||
|
|
||||||
As a general rule of thumb, avoid using global variables, since they make the code difficult to maintain and reason
|
As a general rule of thumb, avoid using global variables, since they make the code difficult to maintain and reason
|
||||||
|
Reference in New Issue
Block a user