使用Moq验证List <>类型的参数?

Chr*_*ley 28 c# moq

使用Moq,我希望能够验证传递给模拟方法调用的参数是否满足某些条件.在这种情况下,我想检查传递给mocked方法的列表是否具有一定的大小:

var mockSomeRepository = new Mock<SomeRepository>();
mockSomeRepository.Setup(m => m.Write(It.IsAny<List<SomeDTO>>())).Verifiable();

var mainClass = new MainClass(mockSomeRepository.Object);
List<SomeDTO> someList = GetListWith25Items();

mainClass.DoRepositoryWrite(someList); // calls SomeRepository.Write(someList);

mockSomeRepository.Verify(m => 
    m.Write(It.Is<List<SomeDTO>>(l => l.Count() == 25)), Times.Once());
Run Code Online (Sandbox Code Playgroud)

验证断言抛出一个异常,表示永远不会以这种方式调用该方法.但是,删除约束并使用Is.Any<List<SomeDTO>>()相反会导致传递.我不确定我是否在这里使用It.Is <>() - 这是我直觉地希望我的测试看起来像但我不确定我是否正确使用框架.我应该如何正确地构建这个测试?

slo*_*oth 27

您可以在模拟上删除对SetupVerifiable的调用.只需使用验证.

我创建了一个小测试项目,这对我有用:

using System;
using System.Collections.Generic;
using System.Linq;
using Moq;

namespace csharp
{
    public class SomeDTO { }

    public class SomeRepository { public virtual void Write(List<SomeDTO> list) { } }

    public class MainClass
    {
        private SomeRepository someRepository;

        public MainClass(SomeRepository someRepository) { this.someRepository = someRepository; }

        public void DoRepositoryWrite(List<SomeDTO> list) { this.someRepository.Write(list); }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var mockSomeRepository = new Mock<SomeRepository>();
            var someList = Enumerable.Repeat(new SomeDTO(), 25).ToList();

            var mainClass = new MainClass(mockSomeRepository.Object);
            mainClass.DoRepositoryWrite(someList);

            mockSomeRepository.Verify(m => m.Write(It.IsAny<List<SomeDTO>>()), Times.Once(), "Write was not called");
            mockSomeRepository.Verify(m => m.Write(It.Is<List<SomeDTO>>(l => l.Count == 25)), Times.Once(), "Write was not called with a 25-element-list");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 也许你的单元测试是正确的,但你的MainClass.DoRepositoryWrite方法不是吗?是否抛出了MockException中列出的"Performed invocations"?如果没有,则mainClass从未调用SomeRepository.Write. (4认同)
  • 这就是你应该使用TDD的原因:p (4认同)
  • 事实证明这是正确的,测试实际上是正常的.谢谢您的帮助! (2认同)
  • 我遇到过这个问题好几次。我为现有方法编写了一个测试,但测试一次又一次失败。寻找测试中的错误,我意识到方法被破坏了,而不是测试:-) (2认同)

Chr*_*ley 6

当我发布这个问题时,我错过了一些重要的细节.我想详细说明实际发生的事情,以防将来帮助某人.我正在测试的方法实际上正在清除传递给mock的列表:

public class SomeDTO { }

public class SomeRepository
{
    public virtual void Write(IEnumerable<SomeDTO> list) { }
}

public class MainClass
{
    private readonly SomeRepository _someRepository;
    private readonly List<SomeDTO> _testList = new List<SomeDTO>(); 

    public MainClass(SomeRepository someRepository)
    {
        _someRepository = someRepository;
    }

    public void DoRepositoryWrite()
    {
        _testList.AddRange(Enumerable.Repeat(new SomeDTO(), 25));
        _someRepository.Write(_testList);
        _testList.Clear();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var mockSomeRepository = new Mock<SomeRepository>();

        var mainClass = new MainClass(mockSomeRepository.Object);

        mainClass.DoRepositoryWrite();

        mockSomeRepository.Verify(m => m.Write(It.IsAny<IEnumerable<SomeDTO>>()), Times.Once(), "Write was not called");
        mockSomeRepository.Verify(m => m.Write(It.Is<IEnumerable<SomeDTO>>(l => l.Count() == 25)), Times.Once(), "Write was not called with a 25-element-list");
    }
}
Run Code Online (Sandbox Code Playgroud)

回顾它似乎有点显而易见,但对我来说这里的内容是模拟挂起对它传递的列表的引用.因此,您需要小心改变该列表的任何副作用.

要正确编写测试,我需要在调用模拟方法时立即检查传递列表的属性.为此,我使用了一个回调:

int listCountAtTimeOfCall = 0;
mockSomeRepository.Setup(
    m => m.Write(It.IsAny<IEnumerable<SomeDTO>>())).Callback
        <IEnumerable<SomeDTO>>(list => listCountAtTimeOfCall = list.Count());

... do the work ...

Assert.AreEqual(listCountAtTimeOfCall, 25);
Run Code Online (Sandbox Code Playgroud)