Moqを使用して、モックされたメソッド呼び出しに渡されるパラメーターで特定の条件が満たされていることを確認できるようにしたいと思います。このシナリオでは、モックされたメソッドに渡されたリストが特定のサイズであることを確認したいと思います。
_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());
_
検証アサートは、メソッドがこの方法で呼び出されないことを示す例外をスローします。ただし、制約を削除してIs.Any<List<SomeDTO>>()
を使用すると、代わりにパスが発生します。ここでIt.Is <>()を適切に使用しているかどうかはわかりません-これは、テストを直感的に見せたいものですが、フレームワークを適切に使用しているかどうかはわかりません。このテストを適切にフレーミングするにはどうすればよいですか?
モックでSetupおよびVerifiableの呼び出しを取り除くことができます。 Verifyを使用するだけです。
私は小さなテストプロジェクトを作成しましたが、これでうまくいきました。
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");
}
}
}
この質問を投稿したとき、いくつかの重要な詳細がありませんでした。それが将来誰かを助ける場合に備えて、私は実際に何が起こっていたかについて詳しく説明したいと思います。テスト中の私のメソッドは、モックに渡されていたリストを実際にクリアしていました:
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");
}
}
振り返ってみると少し明白なようですが、ここでのポイントは、渡されたリストへの参照にモックがかかっていることです。したがって、そのリストを変更する副作用に注意する必要があります。
テストを適切に作成するには、モックされたメソッドが呼び出されたときに、渡されたリストのプロパティをすぐに検査する必要がありました。これを行うには、コールバックを使用しました。
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);