NSubstitute异步返回null,尽管已定义返回对象

5

我有一个单元测试,应该返回指定的对象,但它返回null。

要测试的数据提供程序:

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

测试:

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>???
}

无论如何,t每次都返回null,调试测试时t为空,因为GetDefaultActionPlan没有返回jj而是返回了null?
我错过了什么才能让jj被返回?
编辑:
Eris和Gabe都正确指出我的IEntityStore的模拟不够充分,即使它指定了一个返回值也不会传递到包装的InvokeAsync,因此我还需要模拟InvokeAsync。
Gabe的答案略有偏差,因为传递Arg.Any不能满足InvokeAsync所需的参数。但我不怪他,因为我花了一个小时跟踪继承链,穿过多个项目(这是一家大公司)。他无法访问这些内容。
最终,以下代码导致成功:
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);

当您为 IDataAccessTemplate 创建替代品时,您从未定义其任何方法。await Template.InvokeAsync 是调用 InvokeAsync 的空模拟还是实际调用带有参数的方法? - Eris
1个回答

5

虽然我在你的代码中没有看到这个,但我会假设GetDefaultActionPlan返回了defaultActionPlan变量,并且Template.InvokeAsync是指通过构造函数传递的IDataAccessTemplate。

看起来你忘了为Template.InvokeAsync设置仿真返回值,因为它包装了其他调用,所以它的返回值是你唯一关心的:

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

我相信你已经让我走上了正确的道路,但仍在努力解决一些关于Mocking InvokeAsync包装器的细微差别...似乎它不喜欢contex => Task.FromResult....看起来我需要传递更多内容。 - GPGVM
太好了!我花了一些时间创建了您代码的简单版本,以查看如何构建InvokeAsync模拟调用和Arg.Any在我的测试中起作用。只是为了确认,estore.GetEntityById返回除DefaultActionPlan对象之外的其他内容吗? - Gabe Haack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接