@@ 74,346 74,385 @@ func TestBuildSuccess(t *testing.T) {
a *A
cs []C
}
-
- type Config struct {
- MakeA Provide[*A]
- MakeB Provide[*B]
- MakeCs []Provide[C]
- }
-
- cfg := Config{
- MakeA: MustProvide[*A](func() (*A, error) {
- return &A{val: "hello"}, nil
- }),
- MakeB: MustProvide[*B](func(a *A, cs []C) *B {
- return &B{a: a, cs: cs}
- }),
- MakeCs: []Provide[C]{
- MustProvide[C](C{val: 1}),
- MustProvide[C](func() (C, error) {
- return C{val: 2}, nil
- })},
- }
-
- type Result struct {
- A *A
- B *B
- }
- var res Result
- err := Build(cfg, &res)
- if err != nil {
- t.Fatalf("Build failed: %v", err)
- }
- if res.A == nil {
- t.Fatalf("expected res.A to be populated")
- }
- if res.B == nil {
- t.Fatalf("expected res.B to be populated")
- }
- if res.B.a != res.A {
- t.Fatalf("expected B.a to reference A instance")
- }
- if len(res.B.cs) != 2 {
- t.Fatalf("wrong count. Saw %d", len(res.B.cs))
- }
- if res.B.cs[0].val != 1 {
- t.Fatalf("wrong value")
- }
- if res.B.cs[1].val != 2 {
- t.Fatalf("wrong value")
- }
- if res.A.val != "hello" {
- t.Fatalf("unexpected A value: %s", res.A.val)
- }
-}
-
-func TestBuildSuccess2(t *testing.T) {
- type A struct {
- val string
- }
- type B struct {
- a *A
- }
-
- type Config struct {
- MakeA func() (*A, error)
- MakeB func(*A) (*B, error)
- }
-
- type Result struct {
- A *A
+ type NestedConfig struct {
+ OtherSetting bool
+ NestedDecision func(c NestedConfig) uint
}
-
- cfg := Config{
- MakeA: func() (*A, error) {
- return &A{val: "hello"}, nil
+ type ANum int
+ type ConfigWithInner struct {
+ NestedConfig
+ SomeSetting bool
+ Inner func(c ConfigWithInner) int
+ }
+
+ tests := []struct {
+ name string
+ config interface{}
+ result interface{}
+ verify func(t *testing.T, result interface{})
+ }{
+ {
+ name: "complex dependencies with providers",
+ config: struct {
+ MakeA Provide[*A]
+ MakeB Provide[*B]
+ MakeCs []Provide[C]
+ }{
+ MakeA: MustProvide[*A](func() (*A, error) {
+ return &A{val: "hello"}, nil
+ }),
+ MakeB: MustProvide[*B](func(a *A, cs []C) *B {
+ return &B{a: a, cs: cs}
+ }),
+ MakeCs: []Provide[C]{
+ MustProvide[C](C{val: 1}),
+ MustProvide[C](func() (C, error) {
+ return C{val: 2}, nil
+ }),
+ },
+ },
+ result: &struct {
+ A *A
+ B *B
+ }{},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ A *A
+ B *B
+ })
+ if res.A == nil {
+ t.Fatalf("expected res.A to be populated")
+ }
+ if res.B == nil {
+ t.Fatalf("expected res.B to be populated")
+ }
+ if res.B.a != res.A {
+ t.Fatalf("expected B.a to reference A instance")
+ }
+ if len(res.B.cs) != 2 {
+ t.Fatalf("wrong count. Saw %d", len(res.B.cs))
+ }
+ if res.B.cs[0].val != 1 {
+ t.Fatalf("wrong value")
+ }
+ if res.B.cs[1].val != 2 {
+ t.Fatalf("wrong value")
+ }
+ if res.A.val != "hello" {
+ t.Fatalf("unexpected A value: %s", res.A.val)
+ }
+ },
},
- MakeB: func(a *A) (*B, error) {
- panic("Unexpected call to MakeB")
- // (removed unreachable code after panic)
+ {
+ name: "simple function constructors",
+ config: struct {
+ MakeA func() (*A, error)
+ MakeB func(*A) (*B, error)
+ }{
+ MakeA: func() (*A, error) {
+ return &A{val: "hello"}, nil
+ },
+ MakeB: func(a *A) (*B, error) {
+ panic("Unexpected call to MakeB")
+ },
+ },
+ result: &struct {
+ A *A
+ }{},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ A *A
+ })
+ if res.A == nil {
+ t.Fatalf("expected res.A to be populated")
+ }
+ if res.A.val != "hello" {
+ t.Fatalf("unexpected A value: %s", res.A.val)
+ }
+ },
},
- }
-
- var res Result
- err := Build(cfg, &res)
- if err != nil {
- t.Fatalf("Build failed: %v", err)
- }
- if res.A == nil {
- t.Fatalf("expected res.A to be populated")
- }
- if res.A.val != "hello" {
- t.Fatalf("unexpected A value: %s", res.A.val)
- }
-}
-
-// Test that constructor error is propagated.
-func TestBuildConstructorError(t *testing.T) {
- type A struct{}
- sentinel := errors.New("boom")
-
- type Config struct {
- MakeA func() (*A, error)
- }
- type Result struct {
- A *A
- }
-
- cfg := Config{
- MakeA: func() (*A, error) {
- return nil, sentinel
+ {
+ name: "pre-supplied values",
+ config: struct {
+ A *A
+ MB func(*A) (*B, error)
+ }{
+ A: &A{val: "pre-supplied"},
+ MB: func(a *A) (*B, error) {
+ return &B{a: a}, nil
+ },
+ },
+ result: &struct {
+ A *A
+ B *B
+ }{},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ A *A
+ B *B
+ })
+ if res.A == nil || res.A.val != "pre-supplied" {
+ t.Fatalf("expected pre-supplied A, got %+v", res.A)
+ }
+ if res.B == nil || res.B.a != res.A {
+ t.Fatalf("expected B referencing A, got %+v", res.B)
+ }
+ },
+ },
+ {
+ name: "type aliases",
+ config: struct {
+ A ANum
+ B int
+ }{A: 3, B: 4},
+ result: &struct {
+ A ANum
+ }{},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ A ANum
+ })
+ if res.A != 3 {
+ t.Fatalf("expected A=3, got %v", res.A)
+ }
+ },
+ },
+ {
+ name: "reference config in constructors",
+ config: ConfigWithInner{
+ SomeSetting: true,
+ Inner: func(c ConfigWithInner) int {
+ if c.SomeSetting {
+ return 1
+ }
+ return 0
+ },
+ NestedConfig: NestedConfig{
+ OtherSetting: true,
+ NestedDecision: func(c NestedConfig) uint {
+ if c.OtherSetting {
+ return 1
+ }
+ return 0
+ },
+ },
+ },
+ result: &struct {
+ A int
+ B uint
+ }{},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ A int
+ B uint
+ })
+ if res.A != 1 {
+ t.Fatalf("expected A=1, got %v", res.A)
+ }
+ if res.B != 1 {
+ t.Fatalf("expected B=1, got %v", res.B)
+ }
+ },
},
}
- var res Result
- err := Build(cfg, &res)
- if err == nil {
- t.Fatalf("expected error")
- }
- if !strings.Contains(err.Error(), "MakeA") {
- t.Fatalf("expected error to mention constructor name, got: %v", err)
- }
- if !strings.Contains(err.Error(), "boom") {
- t.Fatalf("expected original error message, got: %v", err)
- }
- if res.A != nil {
- t.Fatalf("result A should not be populated on constructor failure")
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := Build(tt.config, tt.result)
+ if err != nil {
+ t.Fatalf("Build failed: %v", err)
+ }
+ tt.verify(t, tt.result)
+ })
}
}
-// Test missing dependency (constructor requires *A but *A not provided).
-func TestBuildMissingDependency(t *testing.T) {
+func TestBuildErrors(t *testing.T) {
type A struct{}
type B struct {
a *A
}
-
- type Config struct {
- MakeB func(*A) (*B, error)
- }
- type Result struct {
- B *B
- }
-
- cfg := Config{
- MakeB: func(a *A) (*B, error) {
- return &B{a: a}, nil
- },
- }
- var res Result
- err := Build(cfg, &res)
- if err == nil {
- t.Fatalf("expected missing dependency error")
- }
- // Parameter type string should appear (may be *di.A).
- if !strings.Contains(err.Error(), "*di.A") && !strings.Contains(err.Error(), "di.A") {
- t.Fatalf("expected error to mention missing type *di.A, got: %v", err)
- }
- if res.B != nil {
- t.Fatalf("result B should not be populated")
- }
-}
-
-// Test cycle detection between X and Y.
-func TestBuildCycleDetection(t *testing.T) {
type X struct{}
type Y struct{}
- type Config struct {
- MakeX func(*Y) *X
- MakeY func(*X) *Y
- }
- type Result struct {
- X *X
- Y *Y
- }
+ sentinel := errors.New("boom")
- cfg := Config{
- MakeX: func(y *Y) *X {
- return &X{}
+ tests := []struct {
+ name string
+ config interface{}
+ result interface{}
+ expectedErrors []string
+ verify func(t *testing.T, result interface{})
+ }{
+ {
+ name: "constructor error propagation",
+ config: struct {
+ MakeA func() (*A, error)
+ }{
+ MakeA: func() (*A, error) {
+ return nil, sentinel
+ },
+ },
+ result: &struct {
+ A *A
+ }{},
+ expectedErrors: []string{"MakeA", "boom"},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ A *A
+ })
+ if res.A != nil {
+ t.Fatalf("result A should not be populated on constructor failure")
+ }
+ },
},
- MakeY: func(x *X) *Y {
- return &Y{}
+ {
+ name: "missing dependency",
+ config: struct {
+ MakeB func(*A) (*B, error)
+ }{
+ MakeB: func(a *A) (*B, error) {
+ return &B{a: a}, nil
+ },
+ },
+ result: &struct {
+ B *B
+ }{},
+ expectedErrors: []string{"*di.A", "di.A"},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ B *B
+ })
+ if res.B != nil {
+ t.Fatalf("result B should not be populated")
+ }
+ },
},
- }
-
- var res Result
- err := Build(cfg, &res)
- if err == nil {
- t.Fatalf("expected cycle detection error")
- }
- // Both constructors should still be listed as remaining.
- if !strings.Contains(err.Error(), "MakeX") || !strings.Contains(err.Error(), "MakeY") {
- t.Fatalf("expected error to list remaining constructors MakeX and MakeY, got: %v", err)
- }
- if res.X != nil || res.Y != nil {
- t.Fatalf("cycle should prevent any construction; got X=%v Y=%v", res.X, res.Y)
- }
-}
-
-// Ensure that providing pre-supplied value satisfies dependency without constructor for it.
-func TestBuildWithPreSuppliedValue(t *testing.T) {
- type A struct {
- v int
- }
- type B struct {
- a *A
- }
-
- type Config struct {
- // Only constructor for B; A provided directly in config.
- A *A
- MB func(*A) (*B, error)
- }
- type Result struct {
- A *A
- B *B
- }
-
- cfg := Config{
- A: &A{v: 42},
- MB: func(a *A) (*B, error) {
- return &B{a: a}, nil
+ {
+ name: "cycle detection",
+ config: struct {
+ MakeX func(*Y) *X
+ MakeY func(*X) *Y
+ }{
+ MakeX: func(y *Y) *X {
+ return &X{}
+ },
+ MakeY: func(x *X) *Y {
+ return &Y{}
+ },
+ },
+ result: &struct {
+ X *X
+ Y *Y
+ }{},
+ expectedErrors: []string{"MakeX", "MakeY"},
+ verify: func(t *testing.T, result interface{}) {
+ res := result.(*struct {
+ X *X
+ Y *Y
+ })
+ if res.X != nil || res.Y != nil {
+ t.Fatalf("cycle should prevent any construction; got X=%v Y=%v", res.X, res.Y)
+ }
+ },
},
}
- var res Result
- err := Build(cfg, &res)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if res.A == nil || res.A.v != 42 {
- t.Fatalf("expected pre-supplied A (42), got %+v", res.A)
- }
- if res.B == nil || res.B.a != res.A {
- t.Fatalf("expected B referencing A, got %+v", res.B)
- }
-}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := Build(tt.config, tt.result)
+ if err == nil {
+ t.Fatalf("expected error")
+ }
-func TestTypeAlias(t *testing.T) {
- type ANum int
- type Config struct {
- A ANum
- B int
- }
+ errorStr := err.Error()
+ var foundError bool
+ for _, expectedErr := range tt.expectedErrors {
+ if strings.Contains(errorStr, expectedErr) {
+ foundError = true
+ break
+ }
+ }
+ if !foundError {
+ t.Fatalf("expected error to contain one of %v, got: %v", tt.expectedErrors, err)
+ }
- type Result struct {
- A ANum
- }
- var res Result
- err := Build(Config{3, 4}, &res)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if res.A != 3 {
- t.Fatalf("expected A=3, got %v", res.A)
+ tt.verify(t, tt.result)
+ })
}
}
-func TestReferenceConfig(t *testing.T) {
- type NestedConfig struct {
- OtherSetting bool
- NestedDecision func(c NestedConfig) uint
- }
-
- type Config struct {
- NestedConfig
- SomeSetting bool
- Inner func(c Config) int
- }
+func TestNewFunction(t *testing.T) {
+ type ANum int
- type Result struct {
- A int
- B uint
- }
- var res Result
- err := Build(Config{
- SomeSetting: true,
- Inner: func(c Config) int {
- if c.SomeSetting {
- return 1
- }
- return 0
+ tests := []struct {
+ name string
+ config interface{}
+ expected interface{}
+ }{
+ {
+ name: "struct result",
+ config: struct {
+ A int
+ }{A: 42},
+ expected: struct {
+ A int
+ }{A: 42},
},
- NestedConfig: NestedConfig{
- OtherSetting: true,
- NestedDecision: func(c NestedConfig) uint {
- if c.OtherSetting {
- return 1
- }
- return 0
- },
+ {
+ name: "primitive type result",
+ config: struct{ A int }{A: 42},
+ expected: 42,
},
- }, &res)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if res.A != 1 {
- t.Fatalf("expected A=1, got %v", res.A)
}
- if res.B != 1 {
- t.Fatalf("expected B=1, got %v", res.A)
- }
-}
-func TestNew(t *testing.T) {
- type Config struct {
- A int
- }
-
- type Result struct {
- A int
- }
-
- cfg := Config{A: 42}
- res, err := New[Result](cfg)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if res.A != 42 {
- t.Fatalf("expected A=42, got %v", res.A)
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ switch expected := tt.expected.(type) {
+ case struct{ A int }:
+ res, err := New[struct{ A int }](tt.config)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if res.A != expected.A {
+ t.Fatalf("expected A=%d, got %v", expected.A, res.A)
+ }
+ case int:
+ res, err := New[int](tt.config)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if res != expected {
+ t.Fatalf("expected %d, got %v", expected, res)
+ }
+ }
+ })
}
}
-func TestSpecificTypes(t *testing.T) {
- type Config struct {
- A int
- }
-
- cfg := Config{A: 42}
- res, err := New[int](cfg)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if res != 42 {
- t.Fatalf("expected A=42, got %v", res)
+func TestBuildPrimitiveTypes(t *testing.T) {
+ tests := []struct {
+ name string
+ config interface{}
+ expected int
+ }{
+ {
+ name: "build primitive directly",
+ config: struct{ A int }{A: 42},
+ expected: 42,
+ },
}
- var res2 int
- err = Build(Config{A: 42}, &res2)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if res2 != 42 {
- t.Fatalf("expected A=42, got %v", res2)
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var res int
+ err := Build(tt.config, &res)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if res != tt.expected {
+ t.Fatalf("expected %d, got %v", tt.expected, res)
+ }
+ })
}
}