使用Moq模拟方法返回null时会抛出NullReferenceException异常。

6
我正在使用.Net Core 2.0作为我的项目框架,Moq v4.7.145作为我的mocking框架。
我有一个受保护的虚拟方法,通常用于上传字节到云服务。为了能够进行单元测试,我想要模拟这个方法,对于所有其他场景都可以正常工作。
问题出现在当我想让这个方法返回null时。这是为了模拟服务宕机或者实际上返回null。我查看了一些关于SO的问题,但没有一个似乎有效。
我像这样模拟了我的图片提供程序。 mockProvider是云服务的模拟,而LoggerMock是记录器的模拟。
PictureProvider = new Mock<CloudinaryPictureProvider>(mockProvider.Object, LoggerMock.Object)
{
    // CallBase true so it will only overwrite the method I want to be mocked.
    CallBase = true
};

我已经设置了我的模拟方法,就像这样:

PictureProvider.Protected()
            .Setup<Task<ReturnObject>>("UploadAsync", ItExpr.IsAny<ImageUploadParams>())
            .Returns(null as Task<ReturnObject>); 

我已尝试以各种方式转换返回对象,但到目前为止都没有成功。

我正在模拟的方法看起来像这样:

protected virtual async Task<ReturnObject> UploadAsync(ImageUploadParams uploadParams)
{
    var result = await _cloudService.UploadAsync(uploadParams);

    return result == null ? null : new ReturnObject
    {
        // Setting values from the result object
    };
}

调用上述模拟方法的方法如下所示:
public async Task<ReturnObject> UploadImageAsync(byte[] imageBytes, string uploadFolder)
{
    if (imageBytes == null)
    {
        // Exception thrown
    }

    var imageStream = new MemoryStream(imageBytes);

    // It throws the NullReferenceException here.
    var uploadResult = await UploadAsync(new ImageUploadParams
    {
        File = new FileDescription(Guid.NewGuid().ToString(), imageStream),
        EagerAsync = true,
        Folder = uploadFolder
    });

    if (uploadResult?.Error == null)
    {
        // This is basically the if statement I wanted to test.
        return uploadResult;
    }

    {
        // Exception thrown
    }
}

每次在我的public UploadImageAsync方法中调用受保护的(模拟的)方法时,都会抛出NullReferenceException异常:

Message: Test method {MethodName} threw exception:   
System.NullReferenceException: Object reference not set to an instance of an object.

我认为在模拟方法设置方面我漏掉了一些东西,只是我不知道是什么。如果我遗漏了任何内容,请告诉我!


@mjwills 我想这样做,但是复制粘贴整个类和其他包会更加混乱。我希望有人知道为什么 Moq 在我想要简单地使一个方法返回 null 时会抛出错误。 - user4189129
如果您正在尝试从异步方法返回null,则使用“ReturnsAsync((ReturnObject)null)” - FCin
@FCin 我简直不敢相信这么简单.. 它起作用了。你能解释一下为什么如果我不使用ReturnsAsync它会抛出一个nullreferenceexception吗? - user4189129
简单的 .ReturnsAsync(null); 也应该可以工作,对吧? - Daniel Dušek
1个回答

6

使用ReturnsAsync((ReturnObject)null)代替Returns(null as Task<ReturnObject>)。你也可以选择使用Returns(Task.FromResult((ReturnObject)null))

我怀疑发生的情况是Returns期望非null值。当使用ReturnsAsync时,它内部创建了返回一个任务的Task.FromResult

public static IReturnsResult<TMock> ReturnsAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, Func<TResult> valueFunction) where TMock : class
{
    return mock.Returns(() => Task.FromResult(valueFunction()));
}

public static IReturnsResult<TMock> ReturnsAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class
{
    return mock.ReturnsAsync(() => value);
}

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