Skip to content

Commit c90ed23

Browse files
committed
Add module scoping tests for map value groups
Test map value groups behavior across fx.Module boundaries: - Map consumption across child and parent modules - Nested module hierarchies - Global visibility of groups across all modules - Name conflict detection across module boundaries Verified behavior is consistent with existing slice value groups where all modules see the global group state. Moved tests to module_test.go where they belong since they specifically test fx.Module behavior.
1 parent 7e97659 commit c90ed23

File tree

2 files changed

+211
-1
lines changed

2 files changed

+211
-1
lines changed

map_groups_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,4 +468,5 @@ func TestMapValueGroupsEdgeCases(t *testing.T) {
468468
require.Error(t, err)
469469
assert.Contains(t, err.Error(), "value groups may be consumed as slices or string-keyed maps only")
470470
})
471-
}
471+
}
472+

module_test.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,3 +722,212 @@ func TestModuleFailures(t *testing.T) {
722722
}
723723
})
724724
}
725+
726+
// Test types for map value groups in modules
727+
type moduleTestSimpleService interface {
728+
GetName() string
729+
}
730+
731+
type moduleTestBasicService struct {
732+
name string
733+
}
734+
735+
func (b *moduleTestBasicService) GetName() string {
736+
return b.name
737+
}
738+
739+
type moduleTestHandler interface {
740+
Handle(data string) string
741+
}
742+
743+
type moduleTestEmailHandler struct{}
744+
745+
func (e *moduleTestEmailHandler) Handle(data string) string {
746+
return "email: " + data
747+
}
748+
749+
type moduleTestSlackHandler struct{}
750+
751+
func (s *moduleTestSlackHandler) Handle(data string) string {
752+
return "slack: " + data
753+
}
754+
755+
// TestModuleMapValueGroups tests map value groups across module boundaries
756+
func TestModuleMapValueGroups(t *testing.T) {
757+
t.Parallel()
758+
759+
t.Run("map consumption across modules", func(t *testing.T) {
760+
t.Parallel()
761+
762+
type ModuleParams struct {
763+
fx.In
764+
Services map[string]moduleTestSimpleService `group:"services"`
765+
}
766+
767+
// Child module provides services
768+
childModule := fx.Module("child",
769+
fx.Provide(
770+
fx.Annotate(
771+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "child-auth"} },
772+
fx.ResultTags(`name:"auth" group:"services"`),
773+
),
774+
fx.Annotate(
775+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "child-billing"} },
776+
fx.ResultTags(`name:"billing" group:"services"`),
777+
),
778+
),
779+
)
780+
781+
// Parent level provides more services
782+
var params ModuleParams
783+
app := fxtest.New(t,
784+
childModule,
785+
fx.Provide(
786+
fx.Annotate(
787+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "parent-metrics"} },
788+
fx.ResultTags(`name:"metrics" group:"services"`),
789+
),
790+
),
791+
fx.Populate(&params),
792+
)
793+
defer app.RequireStart().RequireStop()
794+
795+
// Should see services from both child module and parent level
796+
require.Len(t, params.Services, 3)
797+
assert.Equal(t, "child-auth", params.Services["auth"].GetName())
798+
assert.Equal(t, "child-billing", params.Services["billing"].GetName())
799+
assert.Equal(t, "parent-metrics", params.Services["metrics"].GetName())
800+
})
801+
802+
t.Run("nested modules with map groups", func(t *testing.T) {
803+
t.Parallel()
804+
805+
type NestedParams struct {
806+
fx.In
807+
Handlers map[string]moduleTestHandler `group:"handlers"`
808+
}
809+
810+
// Deeply nested modules
811+
innerModule := fx.Module("inner",
812+
fx.Provide(
813+
fx.Annotate(
814+
func() moduleTestHandler { return &moduleTestEmailHandler{} },
815+
fx.ResultTags(`name:"email" group:"handlers"`),
816+
),
817+
),
818+
)
819+
820+
middleModule := fx.Module("middle",
821+
innerModule,
822+
fx.Provide(
823+
fx.Annotate(
824+
func() moduleTestHandler { return &moduleTestSlackHandler{} },
825+
fx.ResultTags(`name:"slack" group:"handlers"`),
826+
),
827+
),
828+
)
829+
830+
var params NestedParams
831+
app := fxtest.New(t,
832+
middleModule,
833+
fx.Provide(
834+
fx.Annotate(
835+
func() moduleTestHandler { return &moduleTestEmailHandler{} },
836+
fx.ResultTags(`name:"webhook" group:"handlers"`),
837+
),
838+
),
839+
fx.Populate(&params),
840+
)
841+
defer app.RequireStart().RequireStop()
842+
843+
require.Len(t, params.Handlers, 3)
844+
assert.Contains(t, params.Handlers, "email")
845+
assert.Contains(t, params.Handlers, "slack")
846+
assert.Contains(t, params.Handlers, "webhook")
847+
})
848+
849+
t.Run("modules see global group state", func(t *testing.T) {
850+
t.Parallel()
851+
852+
var childServices map[string]moduleTestSimpleService
853+
var parentServices map[string]moduleTestSimpleService
854+
855+
childModule := fx.Module("child",
856+
fx.Provide(
857+
fx.Annotate(
858+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "child-service"} },
859+
fx.ResultTags(`name:"child" group:"services"`),
860+
),
861+
),
862+
fx.Invoke(fx.Annotate(
863+
func(services map[string]moduleTestSimpleService) {
864+
childServices = services
865+
},
866+
fx.ParamTags(`group:"services"`),
867+
)),
868+
)
869+
870+
app := fxtest.New(t,
871+
childModule,
872+
fx.Provide(
873+
fx.Annotate(
874+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "parent-service"} },
875+
fx.ResultTags(`name:"parent" group:"services"`),
876+
),
877+
),
878+
fx.Invoke(fx.Annotate(
879+
func(services map[string]moduleTestSimpleService) {
880+
parentServices = services
881+
},
882+
fx.ParamTags(`group:"services"`),
883+
)),
884+
)
885+
defer app.RequireStart().RequireStop()
886+
887+
// Both child and parent should see all services (fx groups are global)
888+
require.Len(t, childServices, 2)
889+
assert.Contains(t, childServices, "child")
890+
assert.Contains(t, childServices, "parent")
891+
assert.Equal(t, "child-service", childServices["child"].GetName())
892+
893+
require.Len(t, parentServices, 2)
894+
assert.Contains(t, parentServices, "child")
895+
assert.Contains(t, parentServices, "parent")
896+
assert.Equal(t, "parent-service", parentServices["parent"].GetName())
897+
})
898+
899+
t.Run("name conflicts across modules should fail", func(t *testing.T) {
900+
t.Parallel()
901+
902+
childModule := fx.Module("child",
903+
fx.Provide(
904+
fx.Annotate(
905+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "child-version"} },
906+
fx.ResultTags(`name:"service" group:"services"`),
907+
),
908+
),
909+
)
910+
911+
type ConflictParams struct {
912+
fx.In
913+
Services map[string]moduleTestSimpleService `group:"services"`
914+
}
915+
916+
var params ConflictParams
917+
app := NewForTest(t,
918+
childModule,
919+
fx.Provide(
920+
fx.Annotate(
921+
func() moduleTestSimpleService { return &moduleTestBasicService{name: "parent-version"} },
922+
fx.ResultTags(`name:"service" group:"services"`), // Same name as child
923+
),
924+
),
925+
fx.Populate(&params),
926+
)
927+
928+
// Should fail due to duplicate names across modules
929+
err := app.Err()
930+
require.Error(t, err)
931+
assert.Contains(t, err.Error(), "already provided")
932+
})
933+
}

0 commit comments

Comments
 (0)