fx icon indicating copy to clipboard operation
fx copied to clipboard

Decorations are not applied to parameters passed to group constructors

Open silverspace opened this issue 3 years ago • 0 comments

Describe the bug If I have a struct being injected as part of a group, and that struct contains an interface type with decorations, then the decorations for the interface type are ignored for the group constructor.

For example, suppose we have an interface Animal that has a production implementation of ProdBear, and a unit test decoration that returns MockBunny. When groups are not utilized, my app always populates bunny, since it applies the bunny decorator. However, when I create a Zoo with a group of exhibits, then bear is populated for the Zoo exhibits -- everywhere else bunny is populated.

To Reproduce

package main_test

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"go.uber.org/fx"
	"go.uber.org/fx/fxtest"
)

type Animal interface {
	Name() string
}

type ProdBear struct {
}

var _ Animal = (*ProdBear)(nil)

func NewProdBear() *ProdBear {
	return &ProdBear{}
}

func (p *ProdBear) Name() string {
	return "bear"
}

type MockBunny struct {
}

var _ Animal = (*MockBunny)(nil)

func (r *MockBunny) Name() string {
	return "bunny"
}

func NewMockBunny() *MockBunny {
	return &MockBunny{}
}

type Exhibit struct {
	Animal Animal
}

func NewExhibit(a Animal) *Exhibit {
	return &Exhibit{Animal: a}
}

type ZooParams struct {
	fx.In
	Exhibits []*Exhibit `group:"exhibits"`
}

type Zoo struct {
	Exhibits []*Exhibit
}

func NewZoo(p ZooParams) *Zoo {
	return &Zoo{Exhibits: p.Exhibits}
}

type Trainer struct {
	Animal Animal
}

func NewTrainer(a Animal) *Trainer {
	return &Trainer{Animal: a}
}

func TestFxApp(t *testing.T) {
	var env struct {
		fx.In
		Animal  Animal
		Trainer *Trainer
		Zoo     *Zoo
	}
	app := fxtest.New(t,
		fx.Provide(
			fx.Annotate(NewProdBear, fx.As(new(Animal))),
		),
		fx.Decorate(
			fx.Annotate(NewMockBunny, fx.As(new(Animal))),
		),
		fx.Provide(
			// Using `group` here results in NewExhibit() being passed a bear instead of a bunny.
			fx.Annotate(NewExhibit, fx.ResultTags(`group:"exhibits"`)),
		),
		fx.Provide(NewTrainer),
		fx.Provide(NewZoo),
		fx.Populate(&env),
	)
	defer app.RequireStart().RequireStop()

	assert.Equal(t, "bunny", env.Animal.Name())
	assert.Equal(t, "bunny", env.Trainer.Animal.Name())

	// This fails! Zoo contains a group of exhibits, and the NewExhibit()
	// constructor is only given the prod value, not the decorated value.
	//   expected: "bunny"
	//   actual  : "bear"
	assert.Equal(t, "bunny", env.Zoo.Exhibits[0].Animal.Name())

}

Expected behavior I would expect the above unit test to pass.

Additional context Tested with fx v1.17.1 and dig v1.14.1

silverspace avatar Jul 19 '22 17:07 silverspace