Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoFixture + NSubstitute throws NotASubstituteException for injected auto mock

I'm receiving the following Exception:

NSubstitute.Exceptions.NotASubstituteException: NSubstitute extension methods like .Received() can only be called on objects created using Substitute.For() and related methods.

...when calling chargeService.When():

public class WhenUserTopsUpAndStripeRejectsPaymentRequest
{
    [Theory, AutoNSubstituteData]
    public void it_should_throw_AutoTopUpFailedException(
        [Frozen] StripeChargeService chargeService,
        [Frozen] IRepository repository,
        TopUpUserAccountBalance message,
        TopUpUserAccountBalanceHandler sut) <== has dependency on StripeChargeService
    {
        repository.Find(Arg.Any<ById<User>>())
            .Returns(new User
            {
                AutoTopUpEnabled = true,
                AccountBalance = -15
            });

=====>  chargeService.When(s => s.Create(Arg.Any<StripeChargeCreateOptions>()))
            .DoNotCallBase();

        Assert.Throws<AutoTopUpFailedException>(() => sut.Handle(message));
    }
}

Now, I can of course get around this by doing as the Exception suggests and manually create the StripeChargeService, and then manually create and inject all my dependencies into my SUT, but I'd rather have less code and let AutoFixture do the work.

public class AndStripeRejectsPaymentRequest
{
    [Theory, AutoNSubstituteData]
    public void it_should_throw_AutoTopUpFailedException(
        IMediator mediator,
        IBillingConfig config,
        [Frozen] IRepository repository,
        TopUpDriverAccountBalance message)
    {
        var chargeService = Substitute.ForPartsOf<StripeChargeService>("");

        repository.Find(Arg.Any<ById<Driver, TopUpDriverAccountBalanceHandler.DriverProjection>>())
            .Returns(new TopUpDriverAccountBalanceHandler.DriverProjection
            {
                AutoTopUpEnabled = true,
                AccountBalance = -15
            });

        chargeService.When(s => s.Create(Arg.Any<StripeChargeCreateOptions>()))
            .DoNotCallBase();

        // Manually build SUT, with params declared above.
        var sut = new TopUpDriverAccountBalanceHandler(mediator, repository, config, chargeService);

        Assert.Throws<AutoTopUpFailedException>(() => sut.Handle(message));
    }
}

I thought that by using the AutoNSubstituteCustomization() in the AutoFixture.AutoNSubstitute NuGet package, auto-mocked parameters would be created using NSubstitute. What am I doing wrong?

like image 536
jameskind Avatar asked Jun 03 '16 03:06

jameskind


1 Answers

The AutoNSubstituteCustomization substitutes abstractions only. If you need to create a substitute for a concrete class you should decorate your parameter with the [Ploeh.AutoFixture.AutoNSubstitute.SubstituteAttribute] attribute explicitly:

  [Theory, AutoNSubstituteData]
  public void it_should_throw_AutoTopUpFailedException(
      [Substitute] StripeChargeService chargeService)
  // ...

Please note that (as for v3.47.3) this unfortunately does not work in combination with the [Frozen] attribute (see the corresponding AutoFixture issue).

UPDATED

As a workaround you may substitute an instance of the StripeChargeService type in the AutoNSubstituteDataAttribute (or create a corresponding customization for this purpose):

internal class AutoNSubstituteDataAttribute : AutoDataAttribute
{
  public AutoNSubstituteDataAttribute()
  {
    this.Fixture.Customize(new AutoNSubstituteCustomization());

    // Substitute an instance of the 'StripeChargeService' type
    this.Fixture.Customizations.Insert(
      0,
      new FilteringSpecimenBuilder(
        new MethodInvoker(new NSubstituteMethodQuery()),
        new ExactTypeSpecification(typeof(StripeChargeService))));
  }
}
like image 200
Serhii Shushliapin Avatar answered Sep 28 '22 09:09

Serhii Shushliapin