MoqでExpressionを引数に取るメソッドの備忘録
初稿:
更新:
- 3 min read -

背景
引数に条件式(Expression)を取るメソッドをMoqにSetupしようとした際に少しハマったのでメモ。
環境
- IDE:Visual Studio Community 2019 Ver.16.8.2
 - .NET Version:Core 3.1
 - 言語:C#
 - TestFramework:xUnit 2.4.1
 - Moq Version:4.15.2
 
Moqについて
Moqとは
Moqは. Net用の模擬ライブラリです。 ユニットテストを容易にするために、依存関係との対話をシミュレートして検証することができます。 — moq Tutorial => Getting started with moq
Moq Github
Quickstart · devlooped/moq Wiki
本題
たとえばこのようなUserクラスがあります。
internal class User
{
  public User(UserId userId, UserName userName, UserAge userAge)
  {
    Id = userId ?? throw new ArgumentNullException(nameof(userId));
    Name = userName ?? throw new ArgumentNullException(nameof(userName));
    Age = userAge ?? throw new ArgumentNullException(nameof(userAge));
  }
  public UserId Id { get; }
  public UserName Name { get; }
  public UserAge Age { get; }
}そしてこのような条件式を引数に取るメソッドがあります。
internal interface IUserRepository
{
  IEnumerable<User> FindWithCondition(Expression<Func<User, bool>> predicate);
}このメソッドをMoqにSetupで追加するにはこのように書く。
var moq = new Mock<IUserRepository>(); moq.Setup(x =>
    x.FindWithCondition(It.IsAny<Expression<Func<User, bool>>>()))
.Returns(users);ただこれだとIt.IsAny()を使用しているため、どのような条件でも返す値は同じとなります。
It.Is()で条件によって異なる値を返せないかやってみると、
moq.Setup(x => x.FindWithCondition(It.Is<Expression<Func<User, bool>>>(
              x => x.Age.Value >= 20 && x.Age.Value <= 29)))
    .Returns(users20);
moq.Setup(x => x.FindWithCondition(It.Is<Expression<Func<User, bool>>>(
              x => x.Age.Value >= 30 && x.Age.Value <= 39)))
    .Returns(users30);インテリセンスも効くし普通に書けますが、「Ageの定義が含まれていない」と怒られます。 理由としては、Moqが式の等価の評価を実装していないから。
ではどうするかというと、Returnsの中に条件を書く。
moq.Setup(x => x.FindWithCondition(It.IsAny<Expression<Func<User, bool>>>()))
    .Returns((Expression<Func<User, bool>> predicate) =>
                 users.AsQueryable().Where(predicate));
var userAppService = new UserApplicationService(moq.Object);以上で解決。