【问题标题】:Getting null return values from methods when using Moq使用 Moq 时从方法中获取空返回值
【发布时间】:2016-09-13 10:07:57
【问题描述】:

我以前从未使用 Moq 来模拟上下文,而且我很确定这很容易做到,因为我有一些 RhinoMock 的经验。 但是当我尝试做简单的事情时,使用 UnitTest 检查对象是否正确创建时,我没有得到预期的结果。

假设我有一个用户实体,其中包含一些数据。这是用户类:

public string UserName { get; set; }
public string Password { get; set; }
public Person Person { get; set; }
public List<Role> Roles { get; set; }
public ISecurityService SecurityService { get; set; }
public byte[] Salt { get; set; }

public User(string userName, string password, Person person, ISecurityService securityService)
{
    UserName = userName;
    Person = person;
    Roles = new List<Role>();
    SecurityService = new SecurityService();
    Salt = securityService.GenerateSalt(4);
    Password = securityService.GenerateHashedAndSaltedPassword(password, Salt);
}

我在创建用户时为我的密码使用了加盐和散列。方法GenerateSalt(int salt)GeneraHasedAndSaltedPassword(stiring password, byte[] salt)SecurityService 类的一部分。这就是我实现类的方式:

我有ISecurityService接口:

public interface ISecurityService
{
    byte[] GenerateSalt(int saltSize);
    string GenerateHashedAndSaltedPassword(string password, byte[] salt);
}

SecurityService 类正在实现该接口:

public class SecurityService : ISecurityService
{
    string ISecurityService.GenerateHashedAndSaltedPassword(string password, byte[] salt)
    {
        HashAlgorithm algorithm = new SHA1Managed();
        var plainTextBytes = Encoding.ASCII.GetBytes(password);

        var plainTextWithSaltBytes = AppendByteArray(plainTextBytes, salt);
        var saltedSHA1Bytes = algorithm.ComputeHash(plainTextWithSaltBytes);
        var saltedSHA1WithAppendedSaltBytes = AppendByteArray(saltedSHA1Bytes, salt);

        return "{SSHA}" + Convert.ToBase64String(saltedSHA1WithAppendedSaltBytes);
    }

    byte[] ISecurityService.GenerateSalt(int saltSize)
    {
        var rng = new RNGCryptoServiceProvider();
        var buff = new byte[saltSize];
        rng.GetBytes(buff);
        return buff;
    }

    private byte[] AppendByteArray(byte[] byteArray1, byte[] byteArray2)
    {
        var byteArrayResult =
                new byte[byteArray1.Length + byteArray2.Length];

        for (var i = 0; i < byteArray1.Length; i++)
            byteArrayResult[i] = byteArray1[i];
        for (var i = 0; i < byteArray2.Length; i++)
            byteArrayResult[byteArray1.Length + i] = byteArray2[i];

        return byteArrayResult;
    }
}

在我的单元测试中,我有 Init() 方法:

[SetUp]
public void Init()
{
    var securityServiceMock = new Mock<ISecurityService>();
    userName = "testUser";
    password = "123";
    userBuilder = new UserBuilder(userName, password, person, securityServiceMock.Object);
    user = new User(userName, password, person, securityServiceMock.Object);
}

这是我想为ISecurityService 创建模拟的地方。我将未散列的密码传递给User 类的构造函数。 我的期望是,当创建新用户时,我已经对属于他的密码进行了哈希处理。

但是当我使用调试器查看创建时发生了什么:

Salt = securityService.GenerateSalt(4);

我明白了:

{byte[0]}

密码:

Password = securityService.GenerateHashedAndSaltedPassword(password, Salt);

我得到空值。调试器从不点击此方法。

任何人都知道我在这里做错了什么。创建模拟时是否传递了错误的值,或者我期望得到新对象初始化时无法得到的东西?

【问题讨论】:

  • 你已经为接口创建了模拟,如果你希望它的方法返回你需要设置它们的东西。您不应该期望 mock 会从您的一个类中获取实现,因为您正在模拟接口而不是类。似乎在您的场景中您不需要模拟此接口,而是传递SecurityService 类的真实实例
  • @SergeyS 感谢您的回复。我试过传递 SecurityService 类的真实实例,但结果是一样的:(
  • this var securityServiceMock = new Mock&lt;ISecurityService&gt;(); 只创建空的模拟,其中方法什么都不做。如果你想设置你的模拟,你可以这样做securityServiceMock.Setup(m =&gt; m.GenerateSalt(It.IsAny&lt;int&gt;())).Returns(new byte[] {1, 2, 3});
  • @tym32167 你可能想把它作为答案,这也是我所说的需要做的事情。
  • @nemo_87 取决于您和您的代码。如果您打算在多个测试中重用这个模拟对象,它应该在 Init 中,如果您只在本地使用它 - 在您的测试中。

标签: c# unit-testing moq


【解决方案1】:

这是因为您在实例化 Mock() 对象时使用了MockBehavior.Loose(这是默认值)。

如果您将 [Init] 方法中的实例化替换为:

var securityServiceMock = new Mock<ISecurityService>(MockBehavior.Strict);

它不会返回默认值,而是会抛出一个 MockException。这将迫使您设置对模拟对象的任何调用以明确定义模拟的行为。

我知道 Loose 与 Strick 嘲笑行为之间正在进行一场宗教战争,因此您可以选择自己的阵营,但无论如何,您需要定义在调用给定的嘲笑方法时要采用的行为。

为此,您必须调用 Setup() 方法并传递一个 lambda,该 lambda 将定义应配置的方法和参数(您可以使用静态类 It.Is/IsAny 来代替谓词具体值)。然后,您需要在结果对象上调用Return() 来定义该方法在调用时应返回的内容(您可以使用流利的表示法)

这会给你一些类似的东西:

var myMockedResult = //... whatever
var securityServiceMock = new Mock<ISecurityService>(MockBehavior.Strict);
securityServiceMock
    .Setup(s => s.GenerateHashedAndSaltedPassword(It.IsAny<string>(), It.IsAny<byte[]>))
    .Returns(myMockedResult);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-14
    • 2018-04-05
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多