使用Moq时从方法获取null返回值

时间:2016-09-13 10:07:57

标签: c# unit-testing moq

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

假设我有User实体,里面有一些数据。这是用户类:

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);
}

我正在使用salting和hashing来创建用户密码。方法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);

我得到空值。调试器永远不会遇到这种方法。

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

1 个答案:

答案 0 :(得分:1)

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

如果您通过以下方式替换[Init]方法中的实例化:

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

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

我知道在Loose与Strick嘲讽行为之间会发生一场宗教战争,所以你可以选择你的阵营,但无论如何,你需要定义在调用给定的模拟方法时采用的行为。< / p>

为了做到这一点,你必须调用Setup()方法并传递一个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);