在两种不同的方法上使用 Mock.Setup 与一种方法不匹配,而是 returns null

Using Mock.Setup on two different methods doesn't match on one and instead returns null

我是单元测试的新手。我正在使用 Moq 进行单元测试。我有一种情况,我必须在同一部分模拟两种不同的方法:

我有一个如下所示的操作方法:

public ActionResult Login(someparameters)
{
   //code...
  var user = userRepository.SelectAllUserByEmail(someparamters); //first method
  //....
  var userDetails = userRepository.ValidateUser(someparameters);//second method
}

这是我的单元测试部分:

userrepositoryMock.Setup(r => r.SelectAllUserByEmail(someparameters))
  .Returns(new List<User>() { new User { Salt = strSalt, FundraiserAdminId = fundadmind, StatusCode = statusCode, UserTypeId = userTypeId, HomePageURL = homepageURL, OrganizationId = organizationId } } );

userrepositoryMock.Setup(k => k.ValidateUser(someparamters))
.Returns(new User { Salt = strSalt, FundraiserAdminId = fundadmind, StatusCode = statusCode, UserTypeId = userTypeId, HomePageURL = homepageURL, OrganizationId = organizationId });

但这只会模拟 ValidateUserSelectAllUserByEmail 方法 returns null。

我已经通过添加以下代码解决了这个问题:

 userrepositoryMock.SetReturnsDefault(new User { Salt = strSalt, FundraiserAdminId = fundadmind, StatusCode = statusCode, UserTypeId = userTypeId, HomePageURL = homepageURL, OrganizationId = organizationId });

您没有指定 someparameters 代表什么类型,但我敢打赌其中至少有一个是引用类型(除了简单的字符串)。

对于引用类型(如对象实例),在确切的实例上使用 Moq 的 .Setup 通常不是一个好主意,因为这将需要将完全相同的引用传递给模拟的 class为了使安装程序与 return 提供的输出相匹配。

这是一个重现问题的简单 MVCE。给定以下代码:

public class User
{
    public string Name { get; set; }
}

public interface IMyInterface
{
    string GetUserName(User user);
}

下面的单元测试演示了绑定到特定对象实例 (aUser) 的 Setup 如果另一个引用 (sameUser) 传递给 Mock:

[Test]
public void TestGetUserBad()
{
    var mock = new Mock<IMyInterface>();

    var aUser = new User { Name = "User1" };
    var sameUser = new User { Name = "User1" };

    mock.Setup(x => x.GetUserName(aUser)).Returns<User>(u => u.Name);

    Assert.AreEqual("User1", mock.Object.GetUserName(aUser), 
        "The mock has been setup for aUser, so this works");
    Assert.AreEqual(null, mock.Object.GetUserName(sameUser), 
        "aUser is a different reference than sameUser hence fails");
}

相反,您应该使用 Moq 的 It.Is<>(带谓词)或 It.IsAny<>(任何)matchers 以允许匹配任何满足谓词(如果有)的引用。

[Test]
public void TestGetUserGood()
{
    var mock = new Mock<IMyInterface>();

    var aUser = new User { Name = "User1" };
    var sameUser = new User { Name = "User1" };

    mock.Setup(x => x.GetUserName(It.IsAny<User>())).Returns<User>(u => u.Name);

    Assert.AreEqual("User1", mock.Object.GetUserName(aUser), 
        "The mock has been setup for any user, so this works");
    Assert.AreEqual("User1", mock.Object.GetUserName(sameUser), 
        "The mock has been setup for any user, so this works");
}

编辑

出于兴趣,如果您怀疑您的 Mock 设置之一未按预期匹配(因为如果未找到匹配项,则在使用松散模拟时 Moq 将 return default(T)),您可以临时切换 MockBehaviour to Strict,如果设置不匹配,将抛出。

例如将以下内容应用于 TestGetUserBad

var mock = new Mock<IMyInterface>(MockBehavior.Strict);

结果:

Moq.MockException : IMyInterface.GetUserName(User) invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.