Moq与Task await搭配使用

56

自从我将WCF方法转换为异步后,我的单元测试失败了,我无法找出正确的语法使它们正常工作。

客户端代理类

 public interface IClientProxy
{
     Task DoSomething(CredentialDataList credentialData, string store);
}

服务类

  public class CredentialSync : ICredentialSync
{
    private ICredentialRepository _repository;

    private IClientProxy _client;

    public CredentialSync()
    {
        this._repository = new CredentialRepository();
        this._client = new ClientProxy();
    }

    public CredentialSync(ICredentialRepository repository, IClientProxy client)
    {
        this._repository = repository;
        this._client = client;
    }

   public async Task Synchronise(string payrollNumber)
    {
        try
        {
            if (string.IsNullOrEmpty(payrollNumber))
            {
                .... some code
              }
            else
            {
                CredentialDataList credentialData = new CredentialDataList();
                List<CredentialData> credentialList = new List<CredentialData>();

                // fetch the record from the database
                List<GetCredentialData_Result> data = this._repository.GetCredentialData(payrollNumber);
                var pinData = this._repository.GetCredentialPinData(payrollNumber);

                // get the stores for this employee
                var storeList = data.Where(a => a.StoreNumber != null)
                    .GroupBy(a => a.StoreNumber)
                    .Select(x => new Store { StoreNumber = x.Key.ToString() }).ToArray();

                var credential = this.ExtractCredentialData(data, pinData, payrollNumber);

                credentialList.Add(credential);
                credentialData.CredentialList = credentialList;

                foreach (var store in storeList)
                {       
                  //this line causes an Object reference not set to an instance of an object error
                   await  _client.DoSomething(credentialData, store.StoreNumber);

                }
            }
        }
        catch (Exception ex)
        {
            throw new FaultException<Exception>(ex);
        }
    }

测试类

 /// </summary>
[TestClass]
public class SynchTest
{

    private Mock<ICredentialRepository> _mockRepository;
    private Mock<IClientProxy> _mockService;

    [TestInitialize]
    public void Setup()
    {
       ... some setups for repository which work fine
    }

[TestMethod]      
    public async Task SynchroniseData_WithOneEmployee_CallsReplicateService()
    {
        this._mockService = new Mock<IClientProxy>();
        this._mockService.Setup(x=>x.DoSomething(It.IsAny<CredentialDataList>(), It.IsAny<string>()));
        // arrange
        string payrollNumber = "1";
        CredentialSync service = new CredentialSync(this._mockRepository.Object, this._mockService.Object);

        // act
        await service.Synchronise(payrollNumber);

        // assert                 
        this._mockService.VerifyAll();
    }

调用ClientProxy.DoSomething时出现了错误:

对象引用未设置为对象实例

两个参数都正常。

如果我将我的ClientProxy.DoSomething方法转换为同步方法 (public void DoSomething(...)),代码就可以正常工作,但我确实需要以异步方式调用它。

3个回答

122

DoSomething 返回 null 而不是返回一个 Task,因此在等待时会引发异常。您需要在构建模拟对象时指定它应该返回一个 Task

在这种情况下,您似乎可以简单地使用 Task.FromResult 返回已经完成的任务,因此模拟设置应该像这样:

this._mockService.Setup(...).Returns(Task.FromResult(false));
从下一个版本的.Net(4.6)开始,您可以像这样使用Task.CompletedTask:
this._mockService.Setup(...).Returns(Task.CompletedTask);

8
需要注意的是,在这种情况下,false 只是一个占位符,mock 实际上会忽略 false 这个值,它只需要一个任务来完成,任何任务都可以。因此,false 也可以替换为 "foo" 或其他任何值。 - mcse3010
@mcse3010 确实。 - i3arnon
4
感谢您对4.6语法的点赞,这很简洁并且准确反映了它的实际含义。 - starmandeluxe

34

通过使用ReturnsAsync可以减少代码中的混乱

this._mockService.Setup(...).ReturnsAsync(false);

这样,您就可以删除代码中的Task.FromResult部分。


6
我认为您需要从“DoSomething”模拟中返回“Task”。
this._mockService.Setup(x => x.DoSomething(It.IsAny<CredentialDataList>(), It.IsAny<string>()))
    .Returns(Task.FromResult<int>(0));

或者我最喜欢的是.Returns(Task.Delay(0)); - Michael Plautz

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