func TestErrCyclicDependency(t *testing.T) { Convey("Given I have cyclic dependencies", t, func() { injector := katana.New().ProvideNew(&DepA{}, func(depB *DepB, depD *DepD) *DepA { return &DepA{depB, depD} }) injector.ProvideNew(&DepB{}, func() *DepB { return &DepB{} }) injector.ProvideNew(&DepC{}, func(dep *DepA) *DepC { return &DepC{dep} }) injector.ProvideNew(&DepD{}, func(dep *DepC) *DepD { return &DepD{dep} }) Convey("When I resolve the cyclic dependency", func() { var dep *DepA resolveWithCyclicDependency := func() { injector.Resolve(&dep) } Convey("Then all dependencies are resolved recursively", func() { So(resolveWithCyclicDependency, should.Panic) }) }) }) }
func TestKatanaProvide(t *testing.T) { Convey("Given I have an instance of katana injector with a few value providers", t, func() { depA := &Dependency{} depB := Dependency{} injector := katana.New().Provide(depA, depB) Convey("When I resolve instances of the provided values", func() { var depA1, depA2 *Dependency var depB1, depB2 Dependency injector.Resolve(&depA1, &depA2, &depB1, &depB2) Convey("Then instances of the same type are the same", func() { So(depA1, should.NotBeNil) So(depA2, should.NotBeNil) So(depA1, should.Equal, depA2) So(depB1, should.NotBeNil) So(depB2, should.NotBeNil) So(depB1, should.NotEqual, depB2) So(depB1, should.Resemble, depB2) }) }) }) }
func TestKatanaProvideNewInstance(t *testing.T) { Convey("Given I have an instance of katana injector with a new instance provider of a dependency", t, func() { injector := katana.New().ProvideNew(&Dependency{}, func() *Dependency { return &Dependency{} }) Convey("When I resolve multiple instances of the provided dependency", func() { var dep1, dep2 *Dependency injector.Resolve(&dep1, &dep2) Convey("Then the resolved dependnecies points to different memory address", func() { So(dep1, should.NotEqual, dep2) }) }) }) Convey("Given I have two new instance providers one for *Dependency and another one for *DependencyB", t, func() { injector := katana.New().ProvideNew(&Dependency{}, func() *Dependency { return &Dependency{} }) injector.ProvideNew(&DependencyA{}, func(dep *Dependency) *DependencyA { return &DependencyA{dep} }) Convey("When I resolve multiple instances of *DependencyB which depends on *Dependency", func() { var dep1, dep2 *DependencyA injector.Resolve(&dep1, &dep2) Convey("Then the resolved dependencies point to different memory addresses", func() { So(dep1, should.NotEqual, dep2) Convey("And its dependencies also point to different memory addresses", func() { So(dep1.Dep, should.NotBeNil) So(dep2.Dep, should.NotBeNil) So(dep1.Dep, should.NotEqual, dep2.Dep) }) }) }) }) }
func TestErrInvalidReference(t *testing.T) { Convey("Given I have a provider registered for a given dependency", t, func() { injector := katana.New().ProvideNew(Dependency{}, func() *Dependency { return &Dependency{} }) Convey("Then it panics resolving a reference that is a zero value", func() { var ref *Dependency resolvingZeroValue := func() { injector.Resolve(ref) } So(resolvingZeroValue, should.Panic) }) }) }
func TestErrInvalidProvider(t *testing.T) { // TODO write test case to catch whether the error is properly built Convey("Given I have a provider function with no return value for a given dependency", t, func() { invalidProvider := func() { katana.New().ProvideNew(&DependencyC{}, func() {}) } Convey("Then it fails with an invalid provider error", func() { So(invalidProvider, should.Panic) }) }) Convey("Given I have a provider function with multiple return values for a given dependency", t, func() { invalidProvider := func() { katana.New().ProvideNew(&DependencyC{}, func() (*DependencyC, error) { return nil, nil }) } Convey("Then it fails with an invalid provider error", func() { So(invalidProvider, should.Panic) }) }) }
func TestKatanaProvidesSingletonInstance(t *testing.T) { Convey("Given I have an instance of katana injector with a singleton dependency provider", t, func() { injector := katana.New().ProvideSingleton(&Dependency{}, func() *Dependency { return &Dependency{} }) Convey("When I resolve multiple instances of the provided dependency", func() { var dep1, dep2 *Dependency injector.Resolve(&dep1, &dep2) Convey("Then the resolved dependencies points to the same memory address", func() { So(dep1, should.Equal, dep2) }) }) }) }
func TestInjectorClone(t *testing.T) { Convey("Given I have an injector with a few injectables and cached instances", t, func() { injector := katana.New() injector.Provide(&DependencyB{}) injector.ProvideNew(&Dependency{}, func() *Dependency { return &Dependency{} }) injector.ProvideSingleton(&DependencyA{}, func(dep *Dependency) *DependencyA { return &DependencyA{dep} }) Convey("When I clone the injector", func() { newInjector := injector.Clone() Convey("Then the new injector inherits the original injector providers", func() { var dep *Dependency var depA *DependencyA var depB *DependencyB newInjector.Resolve(&dep, &depA, &depB) So(dep, should.NotBeNil) So(depA, should.NotBeNil) So(depB, should.NotBeNil) }) Convey("And I register new providers with the new injector", func() { newInjector.ProvideSingleton((*InterfaceDependency)(nil), func() InterfaceDependency { return &InterfaceDependencyImpl{} }) Convey("Then the provider is available only in the cloned injector", func() { var dep1, dep2 InterfaceDependency newInjector.Resolve(&dep1) So(dep1, should.NotBeNil) resolveWithOriginalInjector := func() { injector.Resolve(&dep2) } So(resolveWithOriginalInjector, should.Panic) }) }) }) }) }
func TestKatanaProvideAs(t *testing.T) { Convey("Given I have an instance of katana injector with an instance provided as an interface type", t, func() { dep := &InterfaceDependencyImpl{} injector := katana.New().ProvideAs((*InterfaceDependency)(nil), dep) Convey("When I resolve a reference to the provided dependency", func() { var depA InterfaceDependency injector.Resolve(&depA) Convey("Then the reference is successfully resolved", func() { So(depA, should.NotBeNil) So(depA, should.Equal, dep) }) }) }) }
func TestKatanaProvidesNewInstanceOfInterfaceType(t *testing.T) { Convey("Given I have a provider of an interface dependency type", t, func() { injector := katana.New().ProvideNew((*InterfaceDependency)(nil), func() InterfaceDependency { return &InterfaceDependencyImpl{} }) Convey("When I resolve multiple instances of the provided dependency", func() { var dep1, dep2 InterfaceDependency injector.Resolve(&dep1, &dep2) Convey("Then the dependencies are resolved", func() { So(dep1, should.NotEqual, dep2) So(dep1, should.HaveSameTypeAs, &InterfaceDependencyImpl{}) So(dep2, should.HaveSameTypeAs, &InterfaceDependencyImpl{}) }) }) }) }
func TestErrProviderAlreadyRegistered(t *testing.T) { Convey("Given I have a provider registered for a given dependency", t, func() { injector := katana.New().ProvideNew(&DependencyC{}, func() *DependencyC { return &DependencyC{} }) Convey("When I register another provider for that same dependency type", func() { alreadyRegisteredProvider := func() { injector.ProvideNew(&DependencyC{}, func() *DependencyC { return &DependencyC{} }) } Convey("Then it fails with an already registered provider", func() { So(alreadyRegisteredProvider, should.Panic) }) }) }) }
func ExampleKatanaAPI() { // Grabs a new instance of katana.Injector injector := katana.New() // Registers the given instance of Config to be provided as a singleton injectable injector.Provide(Config{ DatastoreURL: "https://myawesomestartup.com/db", CacheTTL: 20000, }) // Registers a constructor function that always provides a new instance of *Cache injector.ProvideNew(&Cache{}, func(config Config) *Cache { return &Cache{config.CacheTTL} }) // Registers a constructor function that always provides a new instance of *Datastore // resolving its dependencies -- Config and *Cache -- as part of the process injector.ProvideNew(&Datastore{}, func(config Config, cache *Cache) *Datastore { return &Datastore{cache, config.DatastoreURL} }) // Registers a constructor function that lazily provides the same instance of *AccountService // resolving its dependencies -- *Datastore -- as part of the process. injector.ProvideSingleton(&AccountService{}, func(db *Datastore) *AccountService { return &AccountService{db} }) var service1, service2 *AccountService injector.Resolve(&service1, &service2) fmt.Println("service1 == service2:", service1 == service2) fmt.Println("service1.Datastore.URL:", service1.Datastore.URL) fmt.Println("service1.Datastore.Cache.TTL:", service1.Datastore.Cache.TTL) // Output: // service1 == service2: true // service1.Datastore.URL: https://myawesomestartup.com/db // service1.Datastore.Cache.TTL: 20000 }
func ExampleHTTP() { injector := katana.New(). ProvideNew(&Database{}, NewDatabase). ProvideNew(&Renderer{}, NewRenderer) http.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) { var db *Database var render *Renderer // Clone creates a copy of the injector, isolating the new registered providers form other threads // We don't want users sharing each other's requests/response writers... injector.Clone(). ProvideAs((*http.ResponseWriter)(nil), w). Provide(req). Resolve(&render, &db) render.JSON(200, db.AllUsers()) }) go func() { log.Fatal(http.ListenAndServe(":8080", nil)) }() done := make(chan bool) go func() { res, _ := http.Get("http://localhost:8080/users") bytes, _ := ioutil.ReadAll(res.Body) var users []*User json.Unmarshal(bytes, &users) fmt.Printf("Users: %v, %v", users[0].Name, users[1].Name) // Output: Users: borges, diego done <- true }() <-done }
func TestKatanaResolvesTransitiveDependencies(t *testing.T) { Convey("Given I have transitive dependencies", t, func() { injector := katana.New().ProvideNew(&DependencyB{}, func(dep *DependencyA) *DependencyB { return &DependencyB{dep} }) injector.ProvideNew(&DependencyA{}, func(dep *Dependency) *DependencyA { return &DependencyA{dep} }) injector.Provide(&Dependency{}) Convey("When I resolve the root dep", func() { var depA *DependencyB injector.Resolve(&depA) Convey("Then all dependencies are resolved recursively", func() { So(depA, should.NotBeNil) So(depA.Dep, should.NotBeNil) So(depA.Dep.Dep, should.NotBeNil) }) }) }) }