使用AutoMoq更改AutoFixture的行为,使方法返回false



假设我有以下接口:

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);
}

然而,我的代码中的一个前提条件是,当调用RosterToTeam时,HasPlayer必须返回false,测试才能通过。

这可以通过创建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,但找不到实现这一点的方法,因为它似乎只适用于属性、参数等。

有人能向我推荐一种方法吗?当以这种方式创建时,可以将所有布尔方法设置为默认返回false

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

这就是我的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()))
{
}
}

没有必要对夹具进行自定义来配置您的mock。你最好在测试中这样做(安排部分(:

[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,它将把返回bool的方法视为特例,并省略对它们的设置。

您可以创建一个行为,将成员重置为返回false,但您将无法自定义mock以返回除false以外的任何值,因为行为在AutoFixture已经创建值的那一刻起作用。

仅通过使用反射来创建或更改mock对象中返回bool的所有成员的设置并不是一项容易的任务。您必须将一大块AutoMoq复制到您的代码库中。幸运的是,我们发布AutoFixture的许可证是所有OSS许可证中最宽松的,因此您可以在没有任何限制的情况下复制和分发您的自定义版本。

您可能想要更改的部分是MockVirtualMethodsCommand中的这一行。你的版本应该是这样的。

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

如果这是我的代码库,在这种情况下,我根本不会使用mock,可能会选择fake。考虑您提出的相同代码,但测试项目将在本地类中实现ITeam接口,而不是使用Moq。

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。(来源于此(

这意味着,除非注入值(fixture.Inject(false)(,否则不能使返回bool的所有方法都返回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>();       
});

不管怎样,这对我很管用。

最新更新