【问题标题】:Mocking a method with a side effect on its argument模拟一个对其参数有副作用的方法
【发布时间】:2022-01-16 01:46:51
【问题描述】:

我在 SomeRepository 中有一个方法,如下所示:

public async Task<Guid> InsertAsync(TEntity entity)
        {
            await _context.Set<TEntity>().AddAsync(entity);
            await _context.SaveChangesAsync();
            return entity.Id;
        }

这里的_context变量只是EntityFrameworkCore DbContext具体实现的一个实例。

现在我想测试一些方法,并以下列方式使用此方法:

public async SomeDto SomeMethod(){

    var guid = await someRepository.InsertAsync(entity);
    //...

    //A bit of code that now relies on entire entity object and it's Id, which gets assigned same value as guid variable  - this is a consequence of a side effect of InsertAsync, illustrated with this line
    var someDto = new SomeDto(entity); 
   
    

    return someDto;
    }

由于我使用的是起订量,我可以在测试时模拟如下:

     // setup
     var guid = Guid.NewGuid();
     someRepositoryMock.Setup(s => s.InsertAsync(It.IsAny<Data.Entities.SomeEntity>()))
                .Returns<Data.Entities.SomeEntity>((a) => 
                {
                    a.Id = guid;
                    return Task.Run(() => a.Id); } 
                );
    var someClass = new SomeClass(someRepositoryMock.Object)
    
    //act - this is a method I'd like to unit test    
    var result = await someClass.SomeMethod();

    //assert - here
    Assert.Equal(guid, result.Id);
    //other asserts on result values here
    

现在,以这段代码为例,问题是当你有一个想要单元测试的方法时,它依赖于它的所有者依赖的副作用,这真的是个好主意在它的模拟中重新创建依赖项的副作用

我这样做的理由是,通过模拟这种依赖关系及其所有​​方法的副作用,我有效地隔离了这种依赖关系的所有方面,这对我正在测试的方法很重要。

但是,我有一种预感,这里有些不对劲。感觉好像相同的功能 - 提到的副作用只是在模拟中重写。在这个例子中,副作用的重现当然非常简单,但是很容易想象一个场景,它可以任意复杂并且更难重现。

理想情况下,所有函数都是纯函数,这个问题就会消失。但事实并非如此。

【问题讨论】:

  • 不是您问题的答案,而是随机观察 - 您可以使用 return Task.FromResult(a.Id) 代替 return Task.FromResult(a.Id) 或使用 .ReturnsAsync(..) 代替 .Returns(..),然后直接返回您的值

标签: c# unit-testing .net-core entity-framework-core moq


【解决方案1】:

我同意代码中有异味。我认为问题在于SomeMethod 执行了一个改变实例状态的副作用,即改变了字段entity 的ID,然后还执行了逻辑——这里省略了。

SomeMethod 似乎侮辱了“单级抽象”原则。因此,我不清楚您打算测试什么。从我在这里看到的情况来看,您可以测试 SomeDto 的构造函数 - 这是一个好主意,但应该直接完成。

所以我的建议是分别测试存储库和SomeDto 的构造函数。此外,我将提取您在示例中省略的逻辑并对其进行单元测试。

剩下的可以接受集成测试。

【讨论】:

  • 这里具体来说,SomeMethod 是某个服务类(贫血域模型)的一部分。它用于几个控制器。之前的团队没有编写任何测试,所以我们要做的第一件事就是尝试在事后编写测试。我得到的具体任务是用单元测试覆盖服务。挑战在于,很多代码不是以测试友好的方式编写的,正如这个例子所展示的那样。
  • 面对遗留代码通常非常复杂。我试图弄清楚似乎实际测试了哪些代码。对我来说,这可能是评估编写测试是否是个好主意的线索。不过,如果我对您的理解有误,请您改写您的问题吗?
猜你喜欢
  • 2011-10-17
  • 2021-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-03
  • 2021-11-10
相关资源
最近更新 更多