带有 AutoNSubstituteCustomization 的 AutoFixture:在 ReadOnly IEnumerable<t> 属性 上设置对象计数

AutoFixture with AutoNSubstituteCustomization: Set object count on ReadOnly IEnumerable<t> property

我的测试要求我在主实体集合的 IEnumerable 属性 中有不同数量的对象。我一直在搜索有关此的文档,但找不到任何内容。这是我的意思的示例(请注意,基本实体是使用 AutoNSubstituteCustomization)

创建的
IFixture fixture = new Fixture().Customize(new AutoNSubstituteCustomization() { ConfigureMembers = true });
var t = fixture.CreateMany<ITransaction>(5)
var service1 = Substitute.For<ITransactionsSvc>();
service1.GetTransactions().ReturnsForAnyArgs(t);
var service2 = Substitute.For<IRequestsSvc>();
service2.GetRequest(default).ReturnsForAnyArgs(
  new Result(){
    TransactionId = t.First().Files.First().RequestId
  }
);

ITransaction 看起来像

public interface ITransaction
{
    long RequestId { get; }
    IEnumerable<FileDef> Files { get; }
    IEnumerable<Comment> Comments { get; }
}

我知道我可以设置 fixture.RepeatCount 来指定全局计数,但我怎样才能为文件和评论设置不同的值?

我已经尝试使用 t.With(x => x.Files, () => fixture.CreateMany<FileDef>(rnd.Next(1,5)) 但它抛出说这是只读 属性.

我也尝试在 t.Files 属性 上使用 NSubstitute .Returns 但由于某种原因, RequestId 的类型在尝试时从 Int64 更改为 Task`1读取 service2 ReturnForAnyArgs 响应的值。

我知道我必须从真实案例中删除一些复杂的内容,这样才能保持简洁,所以我希望我没有删除太多内容并使其易于理解。如果您需要任何精确度,欢迎询问。

子问题: AutoFixture有完整的文档吗?在 AutoFixture website 上,我只能找到非常介绍性的文档。

您遇到的问题似乎与 AutoFixture 无关,而与 NSubstitute 有关。

由于 ITransaction 是一个接口,AutoFixture 会将创建和实例的任务委托给模拟库。在你的情况下是 NSubstitute。

由于您的接口只声明了 getter 而没有 setters,NSubstitute 将为您的接口生成一个动态代理,它也没有任何 public setters .这就是 AutoFixture 无法设置您的属性值的原因。

因此,如果您想继续使用模拟,则必须在界面中指定 public setter 或告诉 AutoFixture 如何使用 NSubstitute [=32] 设置值=].不幸的是,您只能通过为您的界面实现一个 ISpecimenBuilder 工厂然后使用反射来实现第二个选项。

我推荐的另一种方法是将您的界面设置中继到一个伪造的实现,您将手动创建该实现并将具有 public setters .然后你将指示 AutoFixture 将所有请求转发到你的假 class.

接口
[Fact]
public void MyTest()
{
    var fixture = new Fixture();

    fixture.Customize<FakeTransaction>(c => c
        .With(x => x.Files, fixture.CreateMany<FileDef>(2).ToList())
        .With(x => x.Comments, fixture.CreateMany<Comment>(5).ToList()));

    fixture.Customizations.Add(new TypeRelay(typeof(ITransaction), typeof(FakeTransaction)));

    ITransaction mock2 = fixture.Create<ITransaction>();

    Assert.Equal(2, mock2.Files.Count());
    Assert.Equal(5, mock2.Comments.Count());
}

public class FakeTransaction : ITransaction
{
    public long RequestId { get; set; }

    public IEnumerable<FileDef> Files { get; set; }

    public IEnumerable<Comment> Comments { get; set; }
}

提示:为了不在任何地方重复继电器,您可以创建一个将继电器添加到夹具的定制,然后使用 CompositeCustomization 将其与您当前的 NSubstitute 定制相结合。阅读更多 here.

关于你的第二个问题。不幸的是,这是唯一的“官方”文档。目前正在努力发布下一个版本。

更多信息可以参考维护者blogs and this community documentation site. Also there is a cool Pluralsight course available here

事实上,正如@AndreiIvascu 提到的那样,我发现的问题和最干净的解决方案都与 NSubstitute 相关联。由于 NSubstitute 正在创建实例,因此可以使用标准 NSubstitute 调用来配置这些实例。

解决方案就是像我提到的那样使用 Returns 和 ReturnsForAnyArgs 但是 重要的是不要直接在访问 RequestId.

时的第二个替代定义

请注意现在 new Result() 定义之外的行 var requestId = t.First().Files.First().RequestId;

public void MyTest()
{
  IFixture fixture = new Fixture().Customize(new AutoNSubstituteCustomization() { ConfigureMembers = true });

  var t = fixture.Create<ITransaction>();
  t.Files.Returns(fixture.CreateMany<FileDef>(2).ToList());
  t.Comments.Returns(fixture.CreateMany<Comment>(5).ToList());

  var service1 = Substitute.For<ITransactionsSvc>();
  service1.GetTransactions().ReturnsForAnyArgs(t);

  var requestId = t.First().Files.First().RequestId;
  var service2 = Substitute.For<IRequestsSvc>();
  service2.GetRequest(default).ReturnsForAnyArgs(
    new Result(){
      TransactionId = requestId
    }
  );
}