diff --git a/README.md b/README.md index b50575c..4d904bb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Features: - Named dependencies (bindings) - Resolve by functions, variables, and structs - Must helpers that convert errors to panics +- Optional lazy loading of bindings - Global instance for small applications ## Documentation @@ -280,15 +281,30 @@ container.MustCall(c, func(s Shape) { // Other Must Helpers: // container.MustSingleton() +// container.MustSingletonLazy() // container.MustNamedSingleton() +// container.MustNamedSingletonLazy() // container.MustTransient() +// container.MustTransientLazy() // container.MustNamedTransient() +// container.MustNamedTransientLazy() // container.MustCall() // container.MustResolve() // container.MustNamedResolve() // container.MustFill() ``` +### Lazy Binding +Both the named and normal `Singleton` and `Transient` binding calls have a lazy version. +Lazy versions defer calling the provided resolver function until the first time the dependency is resolved. +For singletons the resolver function is called only once and the result is stored. Transient + +Lazy binding calls include: +* container.SingletonLazy() +* container.NamedSingletonLazy() +* container.TransientLazy() +* container.NamedTransientLazy() + ### Performance The package Container inevitably uses reflection for binding and resolving processes. If performance is a concern, try to bind and resolve the dependencies where it runs only once, like the main and init functions. diff --git a/container.go b/container.go index 6f6ac44..b6dfe4d 100644 --- a/container.go +++ b/container.go @@ -9,24 +9,31 @@ import ( "unsafe" ) -// binding holds a resolver and a concrete (if singleton). +// binding holds a resolver and a concrete (if already resolved). // It is the break for the Container wall! type binding struct { - resolver interface{} // resolver is the function that is responsible for making the concrete. - concrete interface{} // concrete is the stored instance for singleton bindings. + resolver interface{} // resolver is the function that is responsible for making the concrete. + concrete interface{} // concrete is the stored instance for singleton bindings. + isSingleton bool // isSingleton is true if the binding is a singleton. } // make resolves the binding if needed and returns the resolved concrete. -func (b binding) make(c Container) (interface{}, error) { +func (b *binding) make(c Container) (interface{}, error) { if b.concrete != nil { return b.concrete, nil } - return c.invoke(b.resolver) + + retVal, err := c.invoke(b.resolver) + if b.isSingleton { + b.concrete = retVal + } + + return retVal, err } // Container holds the bindings and provides methods to interact with them. // It is the entry point in the package. -type Container map[reflect.Type]map[string]binding +type Container map[reflect.Type]map[string]*binding // New creates a new concrete of the Container. func New() Container { @@ -34,7 +41,7 @@ func New() Container { } // bind maps an abstraction to concrete and instantiates if it is a singleton binding. -func (c Container) bind(resolver interface{}, name string, isSingleton bool) error { +func (c Container) bind(resolver interface{}, name string, isSingleton bool, isLazy bool) error { reflectedResolver := reflect.TypeOf(resolver) if reflectedResolver.Kind() != reflect.Func { return errors.New("container: the resolver must be a function") @@ -42,19 +49,44 @@ func (c Container) bind(resolver interface{}, name string, isSingleton bool) err if reflectedResolver.NumOut() > 0 { if _, exist := c[reflectedResolver.Out(0)]; !exist { - c[reflectedResolver.Out(0)] = make(map[string]binding) + c[reflectedResolver.Out(0)] = make(map[string]*binding) } } - concrete, err := c.invoke(resolver) - if err != nil { + if err := c.validateResolverFunction(reflectedResolver); err != nil { return err } + var concrete interface{} + if !isLazy { + var err error + concrete, err = c.invoke(resolver) + if err != nil { + return err + } + } + if isSingleton { - c[reflectedResolver.Out(0)][name] = binding{resolver: resolver, concrete: concrete} + c[reflectedResolver.Out(0)][name] = &binding{resolver: resolver, concrete: concrete, isSingleton: isSingleton} } else { - c[reflectedResolver.Out(0)][name] = binding{resolver: resolver} + c[reflectedResolver.Out(0)][name] = &binding{resolver: resolver, isSingleton: isSingleton} + } + + return nil +} + +func (c Container) validateResolverFunction(funcType reflect.Type) error { + retCount := funcType.NumOut() + + if retCount == 0 || retCount > 2 { + return errors.New("container: resolver function signature is invalid - it must return abstract, or abstract and error") + } + + resolveType := funcType.Out(0) + for i := 0; i < funcType.NumIn(); i++ { + if funcType.In(i) == resolveType { + return fmt.Errorf("container: resolver function signature is invalid - depends on abstract it returns") + } } return nil @@ -69,17 +101,12 @@ func (c Container) invoke(function interface{}) (interface{}, error) { } values := reflect.ValueOf(function).Call(arguments) - - if len(values) == 1 || len(values) == 2 { - if len(values) == 2 && values[1].CanInterface() { - if err, ok := values[1].Interface().(error); ok { - return values[0].Interface(), err - } + if len(values) == 2 && values[1].CanInterface() { + if err, ok := values[1].Interface().(error); ok { + return values[0].Interface(), err } - return values[0].Interface(), nil } - - return nil, errors.New("container: resolver function signature is invalid") + return values[0].Interface(), nil } // arguments returns the list of resolved arguments for a function. @@ -91,10 +118,13 @@ func (c Container) arguments(function interface{}) ([]reflect.Value, error) { for i := 0; i < argumentsCount; i++ { abstraction := reflectedFunction.In(i) if concrete, exist := c[abstraction][""]; exist { - instance, _ := concrete.make(c) + instance, err := concrete.make(c) + if err != nil { + return nil, err + } arguments[i] = reflect.ValueOf(instance) } else { - return nil, errors.New("container: no concrete found for " + abstraction.String()) + return nil, errors.New("container: no concrete found for: " + abstraction.String()) } } @@ -112,24 +142,52 @@ func (c Container) Reset() { // It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface). // The resolver function can have arguments of abstraction that have been declared in the Container already. func (c Container) Singleton(resolver interface{}) error { - return c.bind(resolver, "", true) + return c.bind(resolver, "", true, false) +} + +// SingletonLazy binds an abstraction to concrete lazily in singleton mode. +// The concrete is resolved only when the abstraction is resolved for the first time. +// It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface). +// The resolver function can have arguments of abstraction that have been declared in the Container already. +func (c Container) SingletonLazy(resolver interface{}) error { + return c.bind(resolver, "", true, true) } // NamedSingleton binds a named abstraction to concrete in singleton mode. func (c Container) NamedSingleton(name string, resolver interface{}) error { - return c.bind(resolver, name, true) + return c.bind(resolver, name, true, false) +} + +// NamedSingleton binds a named abstraction to concrete lazily in singleton mode. +// The concrete is resolved only when the abstraction is resolved for the first time. +func (c Container) NamedSingletonLazy(name string, resolver interface{}) error { + return c.bind(resolver, name, true, true) } // Transient binds an abstraction to concrete in transient mode. // It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface). // The resolver function can have arguments of abstraction that have been declared in the Container already. func (c Container) Transient(resolver interface{}) error { - return c.bind(resolver, "", false) + return c.bind(resolver, "", false, false) } -// NamedTransient binds a named abstraction to concrete in transient mode. +// TransientLazy binds an abstraction to concrete lazily in transient mode. +// Normally the resolver will be called during registration, but that is skipped in lazy mode. +// It takes a resolver function that returns the concrete, and its return type matches the abstraction (interface). +// The resolver function can have arguments of abstraction that have been declared in the Container already. +func (c Container) TransientLazy(resolver interface{}) error { + return c.bind(resolver, "", false, true) +} + +// NamedTransient binds a named abstraction to concrete lazily in transient mode. func (c Container) NamedTransient(name string, resolver interface{}) error { - return c.bind(resolver, name, false) + return c.bind(resolver, name, false, false) +} + +// NamedTransient binds a named abstraction to concrete in transient mode. +// Normally the resolver will be called during registration, but that is skipped in lazy mode. +func (c Container) NamedTransientLazy(name string, resolver interface{}) error { + return c.bind(resolver, name, false, true) } // Call takes a receiver function with one or more arguments of the abstractions (interfaces). @@ -214,13 +272,14 @@ func (c Container) Fill(structure interface{}) error { } else if t == "name" { name = s.Type().Field(i).Name } else { - return errors.New( - fmt.Sprintf("container: %v has an invalid struct tag", s.Type().Field(i).Name), - ) + return fmt.Errorf("container: %v has an invalid struct tag", s.Type().Field(i).Name) } if concrete, exist := c[f.Type()][name]; exist { - instance, _ := concrete.make(c) + instance, err := concrete.make(c) + if err != nil { + return err + } ptr := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem() ptr.Set(reflect.ValueOf(instance)) @@ -228,7 +287,7 @@ func (c Container) Fill(structure interface{}) error { continue } - return errors.New(fmt.Sprintf("container: cannot make %v field", s.Type().Field(i).Name)) + return fmt.Errorf("container: cannot make %v field", s.Type().Field(i).Name) } } diff --git a/container_test.go b/container_test.go index 448f1da..ee36562 100644 --- a/container_test.go +++ b/container_test.go @@ -55,11 +55,41 @@ func TestContainer_Singleton(t *testing.T) { assert.NoError(t, err) } +func TestContainer_SingletonLazy(t *testing.T) { + err := instance.SingletonLazy(func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) + + err = instance.Call(func(s1 Shape) { + s1.SetArea(666) + }) + assert.NoError(t, err) + + err = instance.Call(func(s2 Shape) { + a := s2.GetArea() + assert.Equal(t, a, 666) + }) + assert.NoError(t, err) +} + +func TestContainer_Singleton_With_Missing_Dependency_Resolve(t *testing.T) { + err := instance.Singleton(func(db Database) Shape { + return &Circle{a: 13} + }) + assert.EqualError(t, err, "container: no concrete found for: container_test.Database") +} + func TestContainer_Singleton_With_Resolve_That_Returns_Nothing(t *testing.T) { err := instance.Singleton(func() {}) assert.Error(t, err, "container: resolver function signature is invalid") } +func TestContainer_SingletonLazy_With_Resolve_That_Returns_Nothing(t *testing.T) { + err := instance.SingletonLazy(func() {}) + assert.Error(t, err, "container: resolver function signature is invalid") +} + func TestContainer_Singleton_With_Resolve_That_Returns_Error(t *testing.T) { err := instance.Singleton(func() (Shape, error) { return nil, errors.New("app: error") @@ -67,11 +97,27 @@ func TestContainer_Singleton_With_Resolve_That_Returns_Error(t *testing.T) { assert.Error(t, err, "app: error") } +func TestContainer_SingletonLazy_With_Resolve_That_Returns_Error(t *testing.T) { + err := instance.SingletonLazy(func() (Shape, error) { + return nil, errors.New("app: error") + }) + assert.NoError(t, err) + + var s Shape + err = instance.Resolve(&s) + assert.Error(t, err, "app: error") +} + func TestContainer_Singleton_With_NonFunction_Resolver_It_Should_Fail(t *testing.T) { err := instance.Singleton("STRING!") assert.EqualError(t, err, "container: the resolver must be a function") } +func TestContainer_SingletonLazy_With_NonFunction_Resolver_It_Should_Fail(t *testing.T) { + err := instance.SingletonLazy("STRING!") + assert.EqualError(t, err, "container: the resolver must be a function") +} + func TestContainer_Singleton_With_Resolvable_Arguments(t *testing.T) { err := instance.Singleton(func() Shape { return &Circle{a: 666} @@ -85,13 +131,39 @@ func TestContainer_Singleton_With_Resolvable_Arguments(t *testing.T) { assert.NoError(t, err) } +func TestContainer_SingletonLazy_With_Resolvable_Arguments(t *testing.T) { + err := instance.SingletonLazy(func() Shape { + return &Circle{a: 666} + }) + assert.NoError(t, err) + + err = instance.SingletonLazy(func(s Shape) Database { + assert.Equal(t, s.GetArea(), 666) + return &MySQL{} + }) + assert.NoError(t, err) + + var s Shape + err = instance.Resolve(&s) + assert.NoError(t, err) +} + func TestContainer_Singleton_With_Non_Resolvable_Arguments(t *testing.T) { instance.Reset() err := instance.Singleton(func(s Shape) Shape { return &Circle{a: s.GetArea()} }) - assert.EqualError(t, err, "container: no concrete found for container_test.Shape") + assert.EqualError(t, err, "container: resolver function signature is invalid - depends on abstract it returns") +} + +func TestContainer_SingletonLazy_With_Non_Resolvable_Arguments(t *testing.T) { + instance.Reset() + + err := instance.SingletonLazy(func(s Shape) Shape { + return &Circle{a: s.GetArea()} + }) + assert.EqualError(t, err, "container: resolver function signature is invalid - depends on abstract it returns") } func TestContainer_NamedSingleton(t *testing.T) { @@ -106,6 +178,18 @@ func TestContainer_NamedSingleton(t *testing.T) { assert.Equal(t, sh.GetArea(), 13) } +func TestContainer_NamedSingletonLazy(t *testing.T) { + err := instance.NamedSingletonLazy("theCircle", func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) + + var sh Shape + err = instance.NamedResolve(&sh, "theCircle") + assert.NoError(t, err) + assert.Equal(t, sh.GetArea(), 13) +} + func TestContainer_Transient(t *testing.T) { err := instance.Transient(func() Shape { return &Circle{a: 666} @@ -124,11 +208,34 @@ func TestContainer_Transient(t *testing.T) { assert.NoError(t, err) } +func TestContainer_TransientLazy(t *testing.T) { + err := instance.TransientLazy(func() Shape { + return &Circle{a: 666} + }) + assert.NoError(t, err) + + err = instance.Call(func(s1 Shape) { + s1.SetArea(13) + }) + assert.NoError(t, err) + + err = instance.Call(func(s2 Shape) { + a := s2.GetArea() + assert.Equal(t, a, 666) + }) + assert.NoError(t, err) +} + func TestContainer_Transient_With_Resolve_That_Returns_Nothing(t *testing.T) { err := instance.Transient(func() {}) assert.Error(t, err, "container: resolver function signature is invalid") } +func TestContainer_TransientLazy_With_Resolve_That_Returns_Nothing(t *testing.T) { + err := instance.TransientLazy(func() {}) + assert.Error(t, err, "container: resolver function signature is invalid") +} + func TestContainer_Transient_With_Resolve_That_Returns_Error(t *testing.T) { err := instance.Transient(func() (Shape, error) { return nil, errors.New("app: error") @@ -150,6 +257,34 @@ func TestContainer_Transient_With_Resolve_That_Returns_Error(t *testing.T) { assert.Error(t, err, "app: second call error") } +func TestContainer_TransientLazy_With_Resolve_That_Returns_Error(t *testing.T) { + err := instance.TransientLazy(func() (Shape, error) { + return nil, errors.New("app: error") + }) + assert.NoError(t, err) + + var s Shape + err = instance.Resolve(&s) + assert.Error(t, err, "app: error") + + firstCall := true + err = instance.TransientLazy(func() (Database, error) { + if firstCall { + firstCall = false + return &MySQL{}, nil + } + return nil, errors.New("app: second call error") + }) + assert.NoError(t, err) + + var db Database + err = instance.Resolve(&db) + assert.NoError(t, err) + + err = instance.Resolve(&db) + assert.Error(t, err, "app: second call error") +} + func TestContainer_Transient_With_Resolve_With_Invalid_Signature_It_Should_Fail(t *testing.T) { err := instance.Transient(func() (Shape, Database, error) { return nil, nil, nil @@ -157,6 +292,13 @@ func TestContainer_Transient_With_Resolve_With_Invalid_Signature_It_Should_Fail( assert.Error(t, err, "container: resolver function signature is invalid") } +func TestContainer_TransientLazy_With_Resolve_With_Invalid_Signature_It_Should_Fail(t *testing.T) { + err := instance.TransientLazy(func() (Shape, Database, error) { + return nil, nil, nil + }) + assert.Error(t, err, "container: resolver function signature is invalid") +} + func TestContainer_NamedTransient(t *testing.T) { err := instance.NamedTransient("theCircle", func() Shape { return &Circle{a: 13} @@ -169,6 +311,18 @@ func TestContainer_NamedTransient(t *testing.T) { assert.Equal(t, sh.GetArea(), 13) } +func TestContainer_NamedTransientLazy(t *testing.T) { + err := instance.NamedTransientLazy("theCircle", func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) + + var sh Shape + err = instance.NamedResolve(&sh, "theCircle") + assert.NoError(t, err) + assert.Equal(t, sh.GetArea(), 13) +} + func TestContainer_Call_With_Multiple_Resolving(t *testing.T) { err := instance.Singleton(func() Shape { return &Circle{a: 5} @@ -192,6 +346,25 @@ func TestContainer_Call_With_Multiple_Resolving(t *testing.T) { assert.NoError(t, err) } +func TestContainer_Call_With_Dependency_Missing_In_Chain(t *testing.T) { + var instance = container.New() + err := instance.SingletonLazy(func() (Database, error) { + var s Shape + if err := instance.Resolve(&s); err != nil { + return nil, err + } + return &MySQL{}, nil + }) + assert.NoError(t, err) + + err = instance.Call(func(m Database) { + if _, ok := m.(*MySQL); !ok { + t.Error("Expected MySQL") + } + }) + assert.EqualError(t, err, "container: no concrete found for: container_test.Shape") +} + func TestContainer_Call_With_Unsupported_Receiver_It_Should_Fail(t *testing.T) { err := instance.Call("STRING!") assert.EqualError(t, err, "container: invalid function") @@ -206,7 +379,7 @@ func TestContainer_Call_With_Second_UnBounded_Argument(t *testing.T) { assert.NoError(t, err) err = instance.Call(func(s Shape, d Database) {}) - assert.EqualError(t, err, "container: no concrete found for container_test.Database") + assert.EqualError(t, err, "container: no concrete found for: container_test.Database") } func TestContainer_Call_With_A_Returning_Error(t *testing.T) { @@ -402,3 +575,35 @@ func TestContainer_Fill_With_Invalid_Pointer_It_Should_Fail(t *testing.T) { err := instance.Fill(s) assert.EqualError(t, err, "container: invalid structure") } + +func TestContainer_Fill_With_Dependency_Missing_In_Chain(t *testing.T) { + var instance = container.New() + err := instance.Singleton(func() Shape { + return &Circle{a: 5} + }) + assert.NoError(t, err) + + err = instance.NamedSingletonLazy("C", func() (Shape, error) { + var s Shape + if err := instance.NamedResolve(&s, "foo"); err != nil { + return nil, err + } + return &Circle{a: 5}, nil + }) + assert.NoError(t, err) + + err = instance.Singleton(func() Database { + return &MySQL{} + }) + assert.NoError(t, err) + + myApp := struct { + S Shape `container:"type"` + D Database `container:"type"` + C Shape `container:"name"` + X string + }{} + + err = instance.Fill(&myApp) + assert.EqualError(t, err, "container: no concrete found for: container_test.Shape") +} diff --git a/global.go b/global.go index 5f0829e..4b8d4fe 100644 --- a/global.go +++ b/global.go @@ -8,21 +8,41 @@ func Singleton(resolver interface{}) error { return Global.Singleton(resolver) } +// SingletonLazy calls the same method of the global concrete. +func SingletonLazy(resolver interface{}) error { + return Global.SingletonLazy(resolver) +} + // NamedSingleton calls the same method of the global concrete. func NamedSingleton(name string, resolver interface{}) error { return Global.NamedSingleton(name, resolver) } +// NamedSingletonLazy calls the same method of the global concrete. +func NamedSingletonLazy(name string, resolver interface{}) error { + return Global.NamedSingletonLazy(name, resolver) +} + // Transient calls the same method of the global concrete. func Transient(resolver interface{}) error { return Global.Transient(resolver) } +// TransientLazy calls the same method of the global concrete. +func TransientLazy(resolver interface{}) error { + return Global.TransientLazy(resolver) +} + // NamedTransient calls the same method of the global concrete. func NamedTransient(name string, resolver interface{}) error { return Global.NamedTransient(name, resolver) } +// NamedTransientLazy calls the same method of the global concrete. +func NamedTransientLazy(name string, resolver interface{}) error { + return Global.NamedTransientLazy(name, resolver) +} + // Reset calls the same method of the global concrete. func Reset() { Global.Reset() diff --git a/global_test.go b/global_test.go index 1b83568..5b79d04 100644 --- a/global_test.go +++ b/global_test.go @@ -1,9 +1,10 @@ package container_test import ( + "testing" + "github.com/golobby/container/v3" "github.com/stretchr/testify/assert" - "testing" ) func TestSingleton(t *testing.T) { @@ -15,6 +16,15 @@ func TestSingleton(t *testing.T) { assert.NoError(t, err) } +func TestSingletonLazy(t *testing.T) { + container.Reset() + + err := container.SingletonLazy(func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) +} + func TestNamedSingleton(t *testing.T) { container.Reset() @@ -24,6 +34,15 @@ func TestNamedSingleton(t *testing.T) { assert.NoError(t, err) } +func TestNamedSingletonLazy(t *testing.T) { + container.Reset() + + err := container.NamedSingletonLazy("rounded", func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) +} + func TestTransient(t *testing.T) { container.Reset() @@ -33,6 +52,15 @@ func TestTransient(t *testing.T) { assert.NoError(t, err) } +func TestTransientLazy(t *testing.T) { + container.Reset() + + err := container.TransientLazy(func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) +} + func TestNamedTransient(t *testing.T) { container.Reset() @@ -42,6 +70,15 @@ func TestNamedTransient(t *testing.T) { assert.NoError(t, err) } +func TestNamedTransientLazy(t *testing.T) { + container.Reset() + + err := container.NamedTransientLazy("rounded", func() Shape { + return &Circle{a: 13} + }) + assert.NoError(t, err) +} + func TestCall(t *testing.T) { container.Reset() diff --git a/must.go b/must.go index 25e9ea5..8af8770 100644 --- a/must.go +++ b/must.go @@ -7,6 +7,13 @@ func MustSingleton(c Container, resolver interface{}) { } } +// MustSingleton wraps the `SingletonLazy` method and panics on errors instead of returning the errors. +func MustSingletonLazy(c Container, resolver interface{}) { + if err := c.SingletonLazy(resolver); err != nil { + panic(err) + } +} + // MustNamedSingleton wraps the `NamedSingleton` method and panics on errors instead of returning the errors. func MustNamedSingleton(c Container, name string, resolver interface{}) { if err := c.NamedSingleton(name, resolver); err != nil { @@ -14,6 +21,13 @@ func MustNamedSingleton(c Container, name string, resolver interface{}) { } } +// MustNamedSingleton wraps the `NamedSingletonLazy` method and panics on errors instead of returning the errors. +func MustNamedSingletonLazy(c Container, name string, resolver interface{}) { + if err := c.NamedSingletonLazy(name, resolver); err != nil { + panic(err) + } +} + // MustTransient wraps the `Transient` method and panics on errors instead of returning the errors. func MustTransient(c Container, resolver interface{}) { if err := c.Transient(resolver); err != nil { @@ -21,6 +35,13 @@ func MustTransient(c Container, resolver interface{}) { } } +// MustTransientLazy wraps the `TransientLazy` method and panics on errors instead of returning the errors. +func MustTransientLazy(c Container, resolver interface{}) { + if err := c.TransientLazy(resolver); err != nil { + panic(err) + } +} + // MustNamedTransient wraps the `NamedTransient` method and panics on errors instead of returning the errors. func MustNamedTransient(c Container, name string, resolver interface{}) { if err := c.NamedTransient(name, resolver); err != nil { @@ -28,6 +49,13 @@ func MustNamedTransient(c Container, name string, resolver interface{}) { } } +// MustNamedTransient wraps the `NamedTransientLazy` method and panics on errors instead of returning the errors. +func MustNamedTransientLazy(c Container, name string, resolver interface{}) { + if err := c.NamedTransientLazy(name, resolver); err != nil { + panic(err) + } +} + // MustCall wraps the `Call` method and panics on errors instead of returning the errors. func MustCall(c Container, receiver interface{}) { if err := c.Call(receiver); err != nil { diff --git a/must_test.go b/must_test.go index 1f2e5fa..c49116a 100644 --- a/must_test.go +++ b/must_test.go @@ -2,8 +2,9 @@ package container_test import ( "errors" - "github.com/golobby/container/v3" "testing" + + "github.com/golobby/container/v3" ) func TestMustSingleton_It_Should_Panic_On_Error(t *testing.T) { @@ -16,6 +17,14 @@ func TestMustSingleton_It_Should_Panic_On_Error(t *testing.T) { t.Errorf("panic expcted.") } +func TestMustSingletonLazy_It_Should_Panic_On_Error(t *testing.T) { + c := container.New() + + defer func() { recover() }() + container.MustSingletonLazy(c, func() {}) + t.Errorf("panic expcted.") +} + func TestMustNamedSingleton_It_Should_Panic_On_Error(t *testing.T) { c := container.New() @@ -26,6 +35,14 @@ func TestMustNamedSingleton_It_Should_Panic_On_Error(t *testing.T) { t.Errorf("panic expcted.") } +func TestMustNamedSingletonLazy_It_Should_Panic_On_Error(t *testing.T) { + c := container.New() + + defer func() { recover() }() + container.MustNamedSingletonLazy(c, "name", func() {}) + t.Errorf("panic expcted.") +} + func TestMustTransient_It_Should_Panic_On_Error(t *testing.T) { c := container.New() @@ -33,6 +50,23 @@ func TestMustTransient_It_Should_Panic_On_Error(t *testing.T) { container.MustTransient(c, func() (Shape, error) { return nil, errors.New("error") }) + + var resVal Shape + container.MustResolve(c, &resVal) + + t.Errorf("panic expcted.") +} + +func TestMustTransientLazy_It_Should_Panic_On_Error(t *testing.T) { + c := container.New() + + defer func() { recover() }() + container.MustTransientLazy(c, func() { + }) + + var resVal Shape + container.MustResolve(c, &resVal) + t.Errorf("panic expcted.") } @@ -43,6 +77,23 @@ func TestMustNamedTransient_It_Should_Panic_On_Error(t *testing.T) { container.MustNamedTransient(c, "name", func() (Shape, error) { return nil, errors.New("error") }) + + var resVal Shape + container.MustNamedResolve(c, &resVal, "name") + + t.Errorf("panic expcted.") +} + +func TestMustNamedTransientLazy_It_Should_Panic_On_Error(t *testing.T) { + c := container.New() + + defer func() { recover() }() + container.MustNamedTransientLazy(c, "name", func() { + }) + + var resVal Shape + container.MustNamedResolve(c, &resVal, "name") + t.Errorf("panic expcted.") }