wire: add an example and document how to use wire with mocks (google/go-cloud#488)

This commit is contained in:
Robert van Gent
2018-09-28 11:08:06 -07:00
committed by Ross Light
parent 32c3dc8578
commit 2c50843322
6 changed files with 239 additions and 0 deletions

View File

@@ -453,3 +453,24 @@ You may not:
`*Greeter` will be broken. `*Greeter` will be broken.
- Add a provider for `io.Writer` to `GreeterSet`. Injectors might already have - Add a provider for `io.Writer` to `GreeterSet`. Injectors might already have
a provider for `io.Writer` which might conflict with this one. 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.

View 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
}

View 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
}

View File

@@ -0,0 +1 @@
example.com/foo

View 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

View 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
}