di
di copied to clipboard
Possibility to disable singleton behavior.
Does it possible to disable singleton behavior for some dependencies? Probably some ResolveOption
or ProvideOption
for that.
For example I want use goava/di container for testing with mocks. I have two different setups:
-
di.New(...)
with production implementation of interfaces which is used for release builds. -
di.New(...)
with mocks which replace external APIs implementations.
Majority of mock libs assumes that each test recreate mock object but di container always returns same instance of mock object.
I can call di.New
for each test case but it is not looks good.
Hi @ont! Sorry for the late response.
This has not been implemented. I have some ideas about how to do it but have no time.
What kind of testing are you do?
I am trying to do some integration testing of web service. All external APIs must be replaced by mocks and I call controllers methods directly which emulates real http requests to the service.
My tests are looks like this:
// .... interface declaration
type EmailSender interface {
Send(email *sendpulse.Email) error
}
// .... container definition for testing environment
func newContainer() (*di.Container, error) {
return di.New(
internal.ViperModule,
//internal.SendpulseModule, // production version of sendpulse (external email sender API)
SendpulseMockModule, // sendpulse replaced with mock
internal.BoilerModule,
internal.EchoModule, // http controller inside this module requests EmailSender dependency
)
}
// ....
var SendpulseMockModule = di.Options(
di.Provide(EmailSenderProvider, di.As(new(ident.EmailSender))),
)
func EmailSenderProvider(v *viper.Viper) *mocks.EmailSender {
// ...
return &mocks.EmailSender{}
}
// ....
func TestEmail(t *testing.T) {
Convey("Given clean database, fresh echo server and email sender", t, func() {
Reset(func() {
cleanDB()
})
So(cleanDB(), ShouldBeNil)
container, err := newContainer() // every test must recreate whole container for fresh mocks
So(err, ShouldBeNil)
var (
e *echo.Echo
sender *mocks.EmailSender
ctrl *controllers.SomeController
)
err = container.Resolve(&e) // resolving http server
So(err, ShouldBeNil)
err = container.Resolve(&sender) // resolving our mock (implements EmailSender interface)
So(err, ShouldBeNil)
err = container.Resolve(&ctrl) // resolve controller
So(err, ShouldBeNil)
Convey("Email code successfully sent", func() {
email := "[email protected]"
sender.On("Send", mock.AnythingOfType("*sendpulse.Email")).Return(nil) // setup mock
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(fmt.Sprintf(`{"email":"%s"}`, email)))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.SetPath("/api/:version/email")
ctx.SetParamNames("version")
ctx.SetParamValues("1.0")
So(ctrl.EmailSendCode(ctx), ShouldBeNil)
So(rec.Code, ShouldEqual, http.StatusOK)
sender.AssertExpectations(t) // assert calls on mocks
})
Convey("Second test", func() {
// ...
})
}
}
Some highlights:
-
echo.Echo
depends on controller, but doesn't directly calls it in the tests, so it can be returned from di cache - controller directly calls
EmailSender
it can't be cached -
EmailSender
is mock it must be recreated every test case. Because of that it can't be cached.
What's looks wrong for me:
- container recreated every time from the ground (it contains some
di.Ivoke(...)
calls for setup sqlboiler) - manual calls to
container.Resolve
. Probably it is better to move dependencies to convey'sConvey(...)
callback arguments (but it is not part of this issue anyway).
Hi!, @ont! Thanks for the detailed explanation. Is this actually for you? Explicit dependency injection still exists 😄
Currently it is not critical problem. We write container, err := newContainer()
in every test and it is not so bad in performance because we don't have hundreds of tests.
I would like to close this issue as it has become stale. While I am open to PRs, I honestly do not wish to expand the container's functionality any further at this point. Instead, I prefer to concentrate on improving the current codebase and potentially implementing code generation for the existing API