【问题标题】:Faking an Extension Method in a 3rd Party Library在 3rd 方库中伪造扩展方法
【发布时间】:2019-11-14 00:00:19
【问题描述】:

我是否编写了不可测试的方法?由于我正在使用的库有一个作为扩展方法实现的重要方法,所以我似乎无法伪造它。因此,无法测试我的方法。

首先,我将列出我要测试的方法的截断版本。 然后,我将开始尝试使用 FakeItEasy 伪造它。

该方法使用缓存,它是对缓存库LazyCache 中的静态方法的调用,我正在努力伪造:

public async Task<BassRuleEditModel> GetBassRuleEditModel(
    int facilityId,
    int criteriaId,
    int bassRuleId,
    BassRuleEditDto bassRuleEditDto)
{
    var url = _bassRuleService.GetServiceConnectionForFacility(facilityId).Url;
    var dto = bassRuleEditDto ?? _bassRuleService.GetBassRuleEditDto(bassRuleId);

    var bassRuleEditModel = new BassRuleEditModel
    {                
        ...
        LocationList = await GetLocations(url),
        ...
    };

    ...

    return bassRuleEditModel;
}


private async Task<IEnumerable<SelectListItem>> GetLocations(string url)
{
    var cacheKey = string.Concat(CacheKeys.Location, url);

    var selectList = await _appCache.GetOrAddAsync(cacheKey, async () =>
        {
            return new SelectList(await _tasksAndPrioritiesService.ReturnLocationsAsync(url), NameProperty, NameProperty);
        }
    , CacheKeys.DefaultCacheLifetime);

    return selectList;
}

GetOrAddAsync 方法是一种扩展方法。
我只想让假货从缓存中返回一个空的SelectList

注意,AppCache 和所有依赖项都是使用构造函数注入来注入的。

我写的单元测试,我试图伪造AppCache 是:

[Fact]
public async Task Un_Named_Test_Does_Stuff()
{
    var url = "http://somesite.com";
    var referrer = new Uri(url);
    var facilityId = GetRandom.Id();
    var serviceConnectionDto = new ServiceConnectionDto
    {
        Url = "http://google.com" // this url does not matter
    };

    var cacheKey = string.Concat(CacheKeys.Location, serviceConnectionDto.Url);

    A.CallTo(() => _bassRuleService.GetServiceConnectionForFacility(facilityId)).Returns(serviceConnectionDto);
    A.CallTo(() => _urlHelper.Content("~/ServiceSpec/ListView")).Returns(url);
    A.CallTo(() => _appViewService.GetReferrer(url)).Returns(referrer);
    A.CallTo(() => _appCache.GetOrAddAsync(cacheKey, A<Func<Task<SelectList>>>.Ignored))
        .Returns(Task.FromResult(new SelectList(Enumerable.Empty<SelectListItem>().ToList())));

    var editModel = await
        _bassRuleService.GetBassRuleEditModel(GetRandom.Int32(),
            GetRandom.Int32(),
            GetRandom.Int32(),
            null
            );

    var path = editModel.Referrer.AbsolutePath;

    editModel.Referrer.AbsolutePath.ShouldBe(referrer.AbsolutePath);
}

我在测试的构造函数中创建了假货(使用 xUnit):

public BassRuleQueryServiceTests()
{
    _currentUser = A.Fake<ICurrentUser>();
    _bassRuleService = A.Fake<IBassRuleService>();
    _tasksAndPrioritiesService = A.Fake<ITasksAndPrioritiesService>();
    _appViewService = A.Fake<IAppViewService>();
    _urlHelper = A.Fake<IUrlHelper>();
    _applicationDateTime = A.Fake<IApplicationDateTime>();
    _appCache = new MockCacheService();
}    

运行测试的错误是:

消息: FakeItEasy.Configuration.FakeConfigurationException : 当前代理生成器无法拦截方法 LazyCache.AppCacheExtenions.GetOrAddAsync1[Microsoft.AspNetCore.Mvc.Rendering.SelectList](LazyCache.IAppCache cache, System.String key, System.Func1[System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.Rendering.SelectList]] addItemFactory) 原因如下: - 扩展方法不能被拦截,因为它们是静态的。>

我知道伪造静态方法没有启用。我正在寻找解决方案。

我是否需要迫使库作者不要使用扩展方法? (滑稽的问题)

干杯

【问题讨论】:

    标签: unit-testing fakeiteasy lazycache


    【解决方案1】:

    正如您正确指出的那样,扩展是静态方法,静态方法不能伪造。

    扩展方法通常只是用于简化对其扩展类型的操作的包装器;这里似乎就是这种情况。您调用的GetOrAddAsync 扩展方法最终会调用IAppCache.GetOrAddAsync method。所以你应该伪造 that 方法。

    A.CallTo(() => _appCache.GetOrAddAsync(cacheKey, A<Func<ICacheEntry, Task<SelectList>>>.Ignored))
            .Returns(new SelectList(Enumerable.Empty<SelectListItem>().ToList()));
    
    

    这不是很方便,因为这意味着您需要知道扩展方法的作用,但是没有办法绕过它(除了围绕库创建抽象层之外,但 LazyCache 已经是围绕 Microsoft.Extensions.Caching 的抽象.内存...)

    (顺便说一句,您不需要Task.FromResult;当您配置返回Task&lt;T&gt; 的方法时,Returns 方法具有接受T 的重载)


    另外,如果您要返回一个空序列,则根本不需要配置该方法。 FakeItEasy 的默认行为是返回一个空的虚拟 IEnumerable&lt;SelectListItem&gt;

    【讨论】:

    • 我有一种感觉,我将不得不深入研究源代码以找到它所调用的内容并伪造它。您的回答非常有帮助,我非常感谢最后的 2 个提示。我不知道 FakeItEasy。放轻松!
    【解决方案2】:

    作为@Thomas Levesque 出色答案的替代方案,另外两个替代方案是:

    1. 根本不模拟缓存 - 使用真正的 CachingService,因为它在内存中运行,因此包含在测试中是完全合理的。
    2. 为此目的使用 LazyCache 附带的模拟实例 MockCachingService 缓存。

    有关示例,请参阅 https://github.com/alastairtree/LazyCache/wiki/Unit-testing-code-using-LazyCache

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-28
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多