将带有 AutoMoq 的 AutoFixture 的行为更改为 return false 方法

Change behaviour of AutoFixture with AutoMoq to return false for methods

说我有如下界面:

public interface ITeam
{
    bool HasPlayer(IPlayer player);
    void AddPlayer(IPlayer player);
}

我目前有一个测试看起来类似于(使用 AutoMoq):

[Theory]
[MyAutoData]
public void ShouldRosterToTeamWhenPlayerIsNotRostered(Player player, Mock<ITeam> mockedTeam)
{
    player.RosterToTeam(mockedTeam.Object);

    mockedTeam.Verify(team => team.AddPlayer(player), Times.Once);
}

但是,我的代码中的一个先决条件是 HasPlayer 必须 return false 才能在调用 RosterToTeam 时通过测试。

这可以通过创建一个 ICustomization 并直接编写正确的行为来解决,例如:

public class TeamCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Mock<ITeam>>(composer =>
            composer.Do(mock =>
                        mock.Setup(team => team.HasPlayer(It.IsAny<IPlayer>()))
                            .Returns(false)));
    }
}

但是,我希望我的测试始终假定布尔方法的默认值为 false。我已经尝试研究 ISpecimenBuilder,但找不到实现此目的的方法,因为它似乎只适用于属性、参数等。

有没有人能向我推荐一种在以这种方式创建时默认将所有布尔方法设置为 return false 的方法?

编辑: 行为改变背后的罪魁祸首是当 ConfigureMembers = true 设置为 AutoMoqCustomization

这是我的 MyAutoDataAttribute 目前的样子:

public class MyAutoDataAttribute : AutoDataAttribute
{
    public MyAutoDataAttribute() : base(Create)
    {
    }

    private static IFixture Create()
    {
        var fixture = new Fixture();

        fixture.Customize(new AutoMoqCustomization
        {
            ConfigureMembers = true
        });

        fixture.Customize(new TeamCustomization());

        return fixture;
    }
}

对于我的用例,仍然需要 ConfigureMembers = true(并且想删除 TeamCustomization)。

首先,您可能想使用 AutoMoqDataAttribute 创建 ITeam 接口的模拟:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture().Customize(new AutoMoqCustomization()))
    {
    }
}

无需自定义夹具来配置模拟。你最好在测试本身(安排部分)中这样做:

[Theory, AutoMoqData]
public void ShouldRosterToTeamWhenPlayerIsNotRostered(Player player, Mock<ITeam> mockedTeam)
{
    mockedTeam.Setup(t => t.HasPlayer(player)).Returns(false);
    player.RosterToTeam(mockedTeam.Object);
    mockedTeam.Verify(team => team.AddPlayer(player), Times.Once);
}

[Theory, AutoMoqData]
public void ShouldNotRosterToTeamWhenPlayerIsRostered(Player player, Mock<ITeam> mockedTeam)
{
    mockedTeam.Setup(t => t.HasPlayer(player)).Returns(true);
    player.RosterToTeam(mockedTeam.Object);
    mockedTeam.Verify(team => team.AddPlayer(player), Times.Never);
}

最后,简化的 RoastToTeam 实现:

public class Player
{
    public void RosterToTeam(ITeam team)
    {
        if (team.HasPlayer(this))
        {
            return;
        }
        team.AddPlayer(this);
    }
}

考虑到目前提出的所有建议,我认为唯一的其他选择是实施您自己的自定义 AutoMoqCustomizaiton,将方法 returning bool 视为特例并将省略设置它们。

您可以创建一个将成员重置为 return false 的行为,但您将无法将模拟自定义为 return 除 [=] 以外的任何值15=],因为行为在 AutoFixture 已经创建值的那一刻起作用。

通过仅使用反射在模拟对象中为所有成员 returning bool 创建或更改设置并非易事。您必须将大量 AutoMoq 复制到您的代码库中。幸运的是,我们发布 AutoFixture 所依据的许可证是所有 OSS 许可证中最宽松的,因此您可以不受任何限制地复制和分发您的自定义版本。

您可能想要更改的部分是 MockVirtualMethodsCommand 中的这一行 here。您的版本应如下所示。

foreach (var method in methods.Where(x => x.ReturnType != typeof(bool)))

如果这是我的 code-base,在这种情况下我根本不会使用模拟,并且可能会选择假货。考虑您提出的相同代码,但测试项目不使用 Moq,而是在本地 class.

中实现 ITeam 接口
public class FakeTeam : ITeam
{
    public List<IPlayer> Players { get; set; } = new();

    public bool HasPlayer(IPlayer player)
    {
       return this.Players.Contains(player);
    }

    public void AddPlayer(IPlayer player)
    {
       this.Players.Add(player);
    }
}

现在您编写的相同测试看起来像这样。

[Theory, AutoData]
public void ShouldRosterToTeamWhenPlayerIsNotRostered(
    Player player, FakeTeam team)
{
    player.RosterToTeam(team);

    Assert.Contains(player, team.Players); 
}

根据您的业务,您甚至可能想要使用 ITeam 的实际实现,这很好。

据我所知,AutoMoq 直接解析 returntype。 (Source here)

这意味着你不能将 returns bool 的所有方法变为 return false 除非你注入值 (fixture.Inject(false)) -这当然意味着你每次向 AF 询问 bool 时都会得到 false

我在您的方案中所做的是创建一个空接口,该接口继承自我要为其修改行为的接口,并注册它。

像这样:

...
public interface ITeamFake : ITeam {} // empty interface for modifying the mock
...

// Modify ITeam behavior
fixture.Register<Mock<ITeamFake>, Mock<ITeam>>(t => 
    {
        t.Setup(m => m.HasPlayer(It.IsAny<IPlayer>())).Returns(true);       
        return t.As<ITeam>();       
    });
       

无论如何这对我有用。