我之前从未使用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);
我得到空值。调试器永远不会遇到这种方法。
任何人都知道我在这里做错了什么。在创建模拟时我是否传递了错误的值,或者我期望得到一些我无法获得新对象初始化的东西?
答案 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);