Repository: uber-go/fx
Pull request #925 (2022-08-19 07:25:31)

Fix Invoke order when fx.Module is used
Currently, fx.Module's invokes are run after all the invokes provided
at the parent level are invoked.

This makes it difficult for module consumers to control the precise
invoke orders.

This changes the invoke order to run all the invokes in the child
module before running any of the invokes in the parent module, and
clarify that order in the documentation for Invoke.

Solves #918.
Refs GO-1591.

codecov[bot]: # [Codecov](https://codecov.io/gh/uber-go/fx/pull/925?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) Report
> Merging [#925](https://codecov.io/gh/uber-go/fx/pull/925?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (48c1f32) into [master](https://codecov.io/gh/uber-go/fx/commit/7a3d15ec882b5b01028577053f7525e935a2a714?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (7a3d15e) will **not change** coverage.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master     #925   +/-   ##
=======================================
  Coverage   98.72%   98.72%           
=======================================
  Files          30       30           
  Lines        1332     1332           
=======================================
  Hits         1315     1315           
  Misses         11       11           
  Partials        6        6           
```


| [Impacted Files](https://codecov.io/gh/uber-go/fx/pull/925?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) | Coverage Δ | |
|---|---|---|
| [invoke.go](https://codecov.io/gh/uber-go/fx/pull/925/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-aW52b2tlLmdv) | `100.00% <ø> (ø)` | |
| [module.go](https://codecov.io/gh/uber-go/fx/pull/925/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-bW9kdWxlLmdv) | `100.00% <100.00%> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)


Patch:
diff --git a/invoke.go b/invoke.go
index d7ce931..523aef8 100644
--- a/invoke.go
+++ b/invoke.go
@@ -38,6 +38,21 @@ import (
 // was successful.
 // All other returned values are discarded.
 //
+// Invokes registered in [Module]s are run before the ones registered at the
+// scope of the parent. Invokes within the same Module is run in the order
+// they were provided. For example,
+//
+//	fx.New(
+//		fx.Invoke(func3),
+//		fx.Module("someModule",
+//			fx.Invoke(func1),
+//			fx.Invoke(func2),
+//		),
+//		fx.Invoke(func4),
+//	)
+//
+// invokes func1, func2, func3, func4 in that order.
+//
 // Typically, invoked functions take a handful of high-level objects (whose
 // constructors depend on lower-level objects) and introduce them to each
 // other. This kick-starts the application by forcing it to instantiate a
diff --git a/module.go b/module.go
index db6a703..c75ba79 100644
--- a/module.go
+++ b/module.go
@@ -40,6 +40,8 @@ type container interface {
 }
 
 // Module is a named group of zero or more fx.Options.
+// A Module creates a scope in which certain operations are taken
+// place. For more information, see [Decorate], [Replace], or [Invoke].
 func Module(name string, opts ...Option) Option {
 	mo := moduleOption{
 		name:    name,
@@ -151,17 +153,18 @@ func (m *module) provide(p provide) {
 }
 
 func (m *module) executeInvokes() error {
-	for _, invoke := range m.invokes {
-		if err := m.executeInvoke(invoke); err != nil {
+	for _, m := range m.modules {
+		if err := m.executeInvokes(); err != nil {
 			return err
 		}
 	}
 
-	for _, m := range m.modules {
-		if err := m.executeInvokes(); err != nil {
+	for _, invoke := range m.invokes {
+		if err := m.executeInvoke(invoke); err != nil {
 			return err
 		}
 	}
+
 	return nil
 }
 


Test patch:
diff --git a/module_test.go b/module_test.go
index 8e00e89..13be8be 100644
--- a/module_test.go
+++ b/module_test.go
@@ -211,6 +211,36 @@ func TestModuleSuccess(t *testing.T) {
 
 		defer app.RequireStart().RequireStop()
 	})
+
+	t.Run("Invoke order in Modules", func(t *testing.T) {
+		t.Parallel()
+
+		type person struct {
+			age int
+		}
+
+		app := fxtest.New(t,
+			fx.Provide(func() *person {
+				return &person{
+					age: 1,
+				}
+			}),
+			fx.Invoke(func(p *person) {
+				assert.Equal(t, 2, p.age)
+				p.age += 1
+			}),
+			fx.Module("module",
+				fx.Invoke(func(p *person) {
+					assert.Equal(t, 1, p.age)
+					p.age += 1
+				}),
+			),
+			fx.Invoke(func(p *person) {
+				assert.Equal(t, 3, p.age)
+			}),
+		)
+		require.NoError(t, app.Err())
+	})
 }
 
 func TestModuleFailures(t *testing.T) {