简体   繁体   中英

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

My test requires that I have different counts of objects in an IEnumerable property of the main entity collection. I have been searching for documentation about this but can't find anything. Here is a sample of what I mean (note that the base entity is created using 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
  }
);

Where ITransaction would look like

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

I know I could set fixture.RepeatCount to specify the global count but how can I have a different value for Files and Comments?

I already tried using t.With(x => x.Files, () => fixture.CreateMany<FileDef>(rnd.Next(1,5)) but it throws saying this is a readonly property.

I also tried using NSubstitute .Returns on the t.Files property but for some reason, the type of RequestId got changed from Int64 to Task`1 when trying to read the value for service2 ReturnForAnyArgs response.

I know I had to remove some of the complexity from the real case so that is stays concise so I hope I didn't remove to much and kept it understandable. If you need any precisions, feel welcome to ask.

Sub-question : is there any complete documentation on AutoFixture? On AutoFixture website I was only able to find very introductory documentation.

It seems that the issue you're having is not related to AutoFixture but rather with NSubstitute.

Since ITransaction is an interface AutoFixture will delegate the task of creating and instance to the mocking library. In your case that's NSubstitute.

Since your interface only declares getters but no setters, NSubstitute will generate a dynamic proxy, for your interface, that as will as well not have any public setters. This is why AutoFixture is unable to set the values of your properties.

So if you want to continue using the mock, you'll have to either specify a public setter in your interface or tell AutoFixture how to set the values using the NSubstitute API. Unfortunately you'll be able to implement the second option only by implementing an ISpecimenBuilder factory for your interface and then play with reflection.

Another way, which is what I recommend, is to relay the setup of your interface to a fake implementation, which you'll create by hand and which will have the public setters. Then you'll instruct AutoFixture to relay all requests to the interface to your fake 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; }
}

Protip: In order to not repeat the relay everywhere, you could create a customization that will add the relay to the fixture, and then combine it with your current NSubstitute customization using CompositeCustomization . Read more here .

About your second question. Unfortunately that is the only "official" documentation. The current effort is going to releasing the next version.

For more info you can refer to the maintainerblogs and this community documentation site. Also there is a cool Pluralsight course available here .

In fact, as @AndreiIvascu mentionned, the problem AND the cleanest solution I found were linked to NSubstitute. Since NSubstitute is creating the instances, theses instances can be configured using standard NSubstitute calls.

The solution is simply to use Returns and ReturnsForAnyArgs as I mentionned but it is essential that this newly created substitute is not used directly in the definition of a second substitute as it was the case when accessing the RequestId .

Note the line var requestId = t.First().Files.First().RequestId; that is now outside of the new Result() definition.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM