@@ -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