di icon indicating copy to clipboard operation
di copied to clipboard

Possibility to disable singleton behavior.

Open ont opened this issue 3 years ago • 2 comments

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.

ont avatar Sep 20 '21 16:09 ont

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?

defval avatar Sep 26 '21 17:09 defval

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's Convey(...) callback arguments (but it is not part of this issue anyway).

ont avatar Sep 27 '21 09:09 ont

Hi!, @ont! Thanks for the detailed explanation. Is this actually for you? Explicit dependency injection still exists 😄

defval avatar Nov 10 '22 13:11 defval

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.

ont avatar Nov 10 '22 20:11 ont

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

defval avatar May 01 '23 20:05 defval