私はユニットテストとモックを始めたばかりです!データストアとやり取りするコードをカバーする単体テストを作成しようとしています。データアクセスはIRepositoryによってカプセル化されます。
interface IRepository<T> {
....
IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
....
}
IRepositoryの具体的なIoC実装を使用して、テストしようとしているコードは次のようになります。
public class SignupLogic {
private Repository<Company> repo = new Repository<Company>();
public void AddNewCompany(Company toAdd) {
Company existingCompany = this.repo.FindBy(c => c.Name == toAdd.Name).FirstOrDefault();
if(existingCompany != null) {
throw new ArgumentException("Company already exists");
}
repo.Add(Company);
repo.Save();
}
}
ロジックと具体的なリポジトリではなく、SignupLogic.AddNewCompany()自体のロジックをテストするために、IRepositoryをモックアップしてSignupLogicに渡します。モックアップされたリポジトリは次のようになります。
Mock<Repository> repoMock = new Mock<Repository>();
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc")....
名前が「Company Inc」に設定されたCompanyオブジェクトを含むメモリ内IEnumberableを返します。 SignupLogic.AddNewCompanyを呼び出す単体テストは、重複する詳細を持つ会社を設定し、それを渡そうとします。そして、「Company already exists」というメッセージとともにArgumentExceptionがスローされると断言します。このテストは合格していません。
単体テストとAddNewCompany()を実行しながらデバッグすると、existingCompanyは常にnullであるように見えます。必死になって、FindByの呼び出しが次のようになるようにSignupLogic.AddNewCompany()を更新すると、
Company existingCompany = this.repo.FindBy(c => c.Name == "Company Inc").FirstOrDefault();
テストに合格しました。これは、Moqがexactlyのコードにのみ応答していることを示唆しています。これは、テストフィクスチャで設定したものと同じです。明らかに、重複する会社がSignupLogic.AddNewCompanyによって拒否されていることをテストする際には特に役立ちません。
「Is.ItAny」を使用するようにmoq.FindBy(...)を設定しようとしましたが、それでもテストに合格しません。
私が読んでいるすべてのことから、私がしようとしているように式をテストすることは、実際にはここでMoqで実行可能ではないようです。出来ますか?助けてください!
正確に同じ構造(およびリテラル値)を持つExpression
のみが一致することはおそらく正しいでしょう。モックが呼び出されるパラメーターを使用できるようにするReturns()
のオーバーロードを使用することをお勧めします。
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())
.Returns((Expression<Func<Company, bool>> predicate) => ...);
...
、predicate
を使用して、一致する会社を返すことができます(また、一致する会社が予想と異なる場合は、例外をスローすることもできます)。あまりきれいではありませんが、うまくいくと思います。
It.IsAny<>()
を使用して、目的の処理を実行できる必要があります。 It.IsAny<>()
を使用すると、セットアップの戻り値の型を調整するだけで、コードの各ブランチをテストできます。
It.IsAny<Expression<Func<Company, bool>>>()
最初のテストでは、例外をスローする述語に関係なく会社を返します。
var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>{new Company{Name = "Company Inc"}});
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
//Assert the exception was thrown.
2番目のテストでは、戻り値の型を空のリストにします。これにより、addが呼び出されます。
var repoMock = new Mock<IRepository<Company>>();
repoMock.Setup(moq => moq.FindBy(It.IsAny<Expression<Func<Company, bool>>>())).Returns(new List<Company>());
var signupLogic = new SignupLogic(repoMock.Object);
signupLogic.AddNewCompany(new Company {Name = "Company Inc"});
repoMock.Verify(r => r.Add(It.IsAny<Company>()), Times.Once());
通常、所有する型のみをモックします。あなたが所有していないものは、様々な困難のために実際にm笑されるべきではありません。ですから、あなたの質問の名前が示すように、表現をあざけることは進むべき道ではありません。
Moqフレームワーク。関数には.Returns()
を付けることが重要です。そうでない場合、一致しません。あなたがそれをしていないのであれば、それはあなたの問題です。
repoMock.Setup(moq => moq.FindBy(c => c.Name == "Company Inc").Returns(....