Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSubstitute Async returns null despite defined return object

Tags:

I have a unit test that should return the specified object but it is returning null.

The Data Provider to test:

public class PlanDataProvider : BaseDomainServiceProvider, IPlanDataProvider
{
    //CTOR
    public PlanDataProvider(IDataAccessTemplate template, IEntityStore entityStore) : base(template, entityStore)
    {
    }

    public async Task<DefaultActionPlan> GetDefaultActionPlan(string referenceListId)
    {
        var objectId = GetObjectId(referenceListId);

        var defaultActionPlan = await Template.InvokeAsync(context => Task.FromResult(EntityStore.GetEntityById<DefaultActionPlan, ObjectId>
            (
                context.ActivityContext as IDataAccessContext,
                typeof(DefaultActionPlan).FullName,
                objectId
            )));
    }
}

The Test:

public async Task GetPlan_BadPlanID()
{
   //Arrange
   string badPlanId = "57509afbc6b48d3f33b2dfcd";

   ...snip...

   DefaultActionPlan jj = new ObjectId(badPlanId);

   //create EntityStore object
   var dataxs = Substitute.For<IDataAccessContext>();
   var estore = Substitute.For<IEntityStore>();
   estore.GetEntityById<DefaultActionPlan, ObjectId>(
        dataxs, 
        typeof(DefaultActionPlan).FullName, 
        new ObjectId(badPlanId))
   .Returns(Task.FromResult(jj).Result);

   var dataAccessTemplate = Substitute.For<IDataAccessTemplate>();

   PlanDataProvider pdp = new PlanDataProvider(dataAccessTemplate, estore);

   //Act
   var t = await pdp.GetDefaultActionPlan(badPlanId);
   //Now this confuses me as the compiler thinks t is DefaultActionPlan NOT Task<DefaultActionPlan>???
}

Anyway t returns null eferytime and debugging the test t is null because the GetDefaultActionPlan is not returning jj but instead null??

What am I missing to have jj returned?


Edit:

Both Eris and Gabe rightly pointed out that my Mock of the IEntityStore was not sufficient as that value...even though it specifies a return will not get passed to the wrapping InvokeAsync thus I needed to Mock the InvokeAsync as well.

Gabe's answer was slightly off as passing Arg.Any does not satisfy the InvokeAsync needed parms. However I do not fault him for this as I spent an hours tracking the inheritance chain across multiple projects (this is a big company). Something he does not have access to do.

In the end here is the code that resulted in success:

var estore = Substitute.For<IEntityStore>();
var dataAccessTemplate = Substitute.For<IDataAccessTemplate>();
dataAccessTemplate.InvokeAsync(context => Task.FromResult(
      estore.GetEntityById<DefaultActionPlan>(
               dataxs, typeof(DefaultActionPlan).FullName, new ObjectId(badPlanId))))
.ReturnsForAnyArgs(jj);

var pdp = new PlanDataProvider(dataAccessTemplate, estore);
like image 563
GPGVM Avatar asked Jun 15 '16 14:06

GPGVM


1 Answers

While I don't see this in your code, I will assume that GetDefaultActionPlan returns the defaultActionPlan variable, and that Template.InvokeAsync is referring to the IDataAccessTemplate passed in via the constructor.

It looks like you are missing a mock return value for Template.InvokeAsync, and since it is wrapping the other call its return value is the only one you care about:

var estore = Substitute.For<IEntityStore>();
var dataAccessTemplate = Substitute.For<IDataAccessTemplate>();
dataAccessTemplate.InvokeAsync(context => Task.FromResult(Arg.Any<DefaultActionPlan>)
    .ReturnsForAnyArgs(jj);

var pdp = new PlanDataProvider(dataAccessTemplate, estore);
like image 154
Gabe Haack Avatar answered Sep 28 '22 02:09

Gabe Haack