wire: add an example and document how to use wire with mocks (google/go-cloud#488)
This commit is contained in:
committed by
Ross Light
parent
32c3dc8578
commit
2c50843322
21
README.md
21
README.md
@@ -453,3 +453,24 @@ You may not:
|
||||
`*Greeter` will be broken.
|
||||
- Add a provider for `io.Writer` to `GreeterSet`. Injectors might already have
|
||||
a provider for `io.Writer` which might conflict with this one.
|
||||
|
||||
### Mocking
|
||||
|
||||
There are two approaches for creating an injected app with mocked dependencies.
|
||||
Examples of both approaches are shown
|
||||
[here](https://github.com/google/go-cloud/tree/master/wire/internal/wire/testdata/ExampleWithMocks/foo).
|
||||
|
||||
#### Approach A: Pass mocks to the injector
|
||||
|
||||
Create a test-only injector that takes all of the mocks as arguments; the
|
||||
argument types must be the interface types the mocks are mocking. `wire.Build`
|
||||
can't include providers for the mocked dependencies without creating conflicts,
|
||||
so if you're using provider set(s) you will need to define one that doesn't
|
||||
include the mocked types.
|
||||
|
||||
#### Approach B: Return the mocks from the injector
|
||||
|
||||
Create a new struct that includes the app plus all of the dependencies you want
|
||||
to mock. Create a test-only injector that returns this struct, give it providers
|
||||
for the concrete mock types, and use `wire.Bind` to tell Wire that the
|
||||
concrete mock types should be used to fulfill the appropriate interface.
|
||||
|
||||
120
internal/wire/testdata/ExampleWithMocks/foo/foo.go
vendored
Normal file
120
internal/wire/testdata/ExampleWithMocks/foo/foo.go
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2018 The Go Cloud Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This test demonstrates how to use mocks with wire.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cloud/wire"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a "real" greeter.
|
||||
// Greet() will include the real current time, so elide it for repeatable
|
||||
// tests.
|
||||
fmt.Printf("Real time greeting: %s [current time elided]\n", initApp().Greet()[0:15])
|
||||
|
||||
// There are two approaches for creating an app with mocks.
|
||||
|
||||
// Approach A: create the mocks manually, and pass them to an injector.
|
||||
// This approach is useful if you need to prime the mocks beforehand.
|
||||
fmt.Println("Approach A")
|
||||
mt := newMockTimer()
|
||||
mockedApp := initMockedAppFromArgs(mt)
|
||||
fmt.Println(mockedApp.Greet()) // prints greeting with time = zero time
|
||||
mt.T = mt.T.AddDate(1999, 0, 0)
|
||||
fmt.Println(mockedApp.Greet()) // prints greeting with time = year 2000
|
||||
|
||||
// Approach B: allow the injector to create the mocks, and return a struct
|
||||
// that includes the resulting app plus the mocks.
|
||||
fmt.Println("Approach B")
|
||||
appWithMocks := initMockedApp()
|
||||
fmt.Println(appWithMocks.app.Greet()) // prints greeting with time = zero time
|
||||
appWithMocks.mt.T = appWithMocks.mt.T.AddDate(999, 0, 0)
|
||||
fmt.Println(appWithMocks.app.Greet()) // prints greeting with time = year 1000
|
||||
}
|
||||
|
||||
// appSet is a provider set for creating a real app.
|
||||
var appSet = wire.NewSet(
|
||||
app{},
|
||||
greeter{},
|
||||
wire.InterfaceValue(new(timer), realTime{}),
|
||||
)
|
||||
|
||||
// appSetWithoutMocks is a provider set for creating an app with mocked
|
||||
// dependencies. The mocked dependencies are omitted and must be provided as
|
||||
// arguments to the injector.
|
||||
// It is used for Approach A.
|
||||
var appSetWithoutMocks = wire.NewSet(
|
||||
app{},
|
||||
greeter{},
|
||||
)
|
||||
|
||||
// mockAppSet is a provider set for creating a mocked app, including the mocked
|
||||
// dependencies.
|
||||
// It is used for Approach B.
|
||||
var mockAppSet = wire.NewSet(
|
||||
app{},
|
||||
greeter{},
|
||||
appWithMocks{},
|
||||
// For each mocked dependency, add a provider and use wire.Bind to bind
|
||||
// the concrete type to the relevant interface.
|
||||
newMockTimer,
|
||||
wire.Bind(new(timer), new(mockTimer)),
|
||||
)
|
||||
|
||||
type timer interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// realTime implements timer with the real time.
|
||||
type realTime struct{}
|
||||
|
||||
func (realTime) Now() time.Time { return time.Now() }
|
||||
|
||||
// mockTimer implements timer using a mocked time.
|
||||
type mockTimer struct {
|
||||
T time.Time
|
||||
}
|
||||
|
||||
func newMockTimer() *mockTimer { return &mockTimer{} }
|
||||
func (m *mockTimer) Now() time.Time { return m.T }
|
||||
|
||||
// greeter issues greetings with the time provided by T.
|
||||
type greeter struct {
|
||||
T timer
|
||||
}
|
||||
|
||||
func (g greeter) Greet() string {
|
||||
return fmt.Sprintf("Good day! It is %v", g.T.Now())
|
||||
}
|
||||
|
||||
type app struct {
|
||||
g greeter
|
||||
}
|
||||
|
||||
func (a app) Greet() string {
|
||||
return a.g.Greet()
|
||||
}
|
||||
|
||||
// appWithMocks is used for Approach B, to return the app plus its mocked
|
||||
// dependencies.
|
||||
type appWithMocks struct {
|
||||
app app
|
||||
mt *mockTimer
|
||||
}
|
||||
42
internal/wire/testdata/ExampleWithMocks/foo/wire.go
vendored
Normal file
42
internal/wire/testdata/ExampleWithMocks/foo/wire.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 The Go Cloud Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//+build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/google/go-cloud/wire"
|
||||
)
|
||||
|
||||
// initApp returns a real app.
|
||||
func initApp() *app {
|
||||
wire.Build(appSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// initMockedAppFromArgs returns an app with mocked dependencies provided via
|
||||
// arguments (Approach A). Note that the argument's type is the interface
|
||||
// type (timer), but the concrete mock type should be passed.
|
||||
func initMockedAppFromArgs(mt timer) *app {
|
||||
wire.Build(appSetWithoutMocks)
|
||||
return nil
|
||||
}
|
||||
|
||||
// initMockedApp returns an app with its mocked dependencies, created
|
||||
// via providers (Approach B).
|
||||
func initMockedApp() *appWithMocks {
|
||||
wire.Build(mockAppSet)
|
||||
return nil
|
||||
}
|
||||
1
internal/wire/testdata/ExampleWithMocks/pkg
vendored
Normal file
1
internal/wire/testdata/ExampleWithMocks/pkg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
example.com/foo
|
||||
7
internal/wire/testdata/ExampleWithMocks/want/program_out.txt
vendored
Normal file
7
internal/wire/testdata/ExampleWithMocks/want/program_out.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
Real time greeting: Good day! It is [current time elided]
|
||||
Approach A
|
||||
Good day! It is 0001-01-01 00:00:00 +0000 UTC
|
||||
Good day! It is 2000-01-01 00:00:00 +0000 UTC
|
||||
Approach B
|
||||
Good day! It is 0001-01-01 00:00:00 +0000 UTC
|
||||
Good day! It is 1000-01-01 00:00:00 +0000 UTC
|
||||
48
internal/wire/testdata/ExampleWithMocks/want/wire_gen.go
vendored
Normal file
48
internal/wire/testdata/ExampleWithMocks/want/wire_gen.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate wire
|
||||
//+build !wireinject
|
||||
|
||||
package main
|
||||
|
||||
// Injectors from wire.go:
|
||||
|
||||
func initApp() *app {
|
||||
mainTimer := _wireRealTimeValue
|
||||
mainGreeter := greeter{
|
||||
T: mainTimer,
|
||||
}
|
||||
mainApp := &app{
|
||||
g: mainGreeter,
|
||||
}
|
||||
return mainApp
|
||||
}
|
||||
|
||||
var (
|
||||
_wireRealTimeValue = realTime{}
|
||||
)
|
||||
|
||||
func initMockedAppFromArgs(mt timer) *app {
|
||||
mainGreeter := greeter{
|
||||
T: mt,
|
||||
}
|
||||
mainApp := &app{
|
||||
g: mainGreeter,
|
||||
}
|
||||
return mainApp
|
||||
}
|
||||
|
||||
func initMockedApp() *appWithMocks {
|
||||
mainMockTimer := newMockTimer()
|
||||
mainGreeter := greeter{
|
||||
T: mainMockTimer,
|
||||
}
|
||||
mainApp := app{
|
||||
g: mainGreeter,
|
||||
}
|
||||
mainAppWithMocks := &appWithMocks{
|
||||
app: mainApp,
|
||||
mt: mainMockTimer,
|
||||
}
|
||||
return mainAppWithMocks
|
||||
}
|
||||
Reference in New Issue
Block a user