我应该如何使用 Moq 测试方法之间的依赖关系?
How should I test dependencies between methods using Moq?
我想确保我正在测试的方法对某些参数使用相同值的模拟方法进行两次调用。这些值是在被测试的方法内部生成的,因此在设置测试时是未知的。在这种情况下,被测试的方法是在 Redis 中存储两个相关的项目,但这与问题无关;我问的是嘲笑。我想确认我所做的方式是最好的方式,它确实有效。也许我错过了 Moq 的其他一些功能可以让这件事以更好的方式完成。
这就是我的。
IRedisClient _redisClientMock = new Mock<IRedisClient>(MockBehavior.Strict);
string token = null;
DateTime expires = default(DateTime);
_redisClientMock
.Setup(x => x.Set(KEY_PREFIX, It.IsAny<string>(), $"{TEST_ID},{TEST_NAME}", It.IsAny<DateTime>()))
.Callback<string, string, string, DateTime?>((p, k, v, e) =>
{
token = k;
expires = e.Value;
});
_redisClientMock
.Setup(x => x.Set(KEY_PREFIX, TEST_ID, It.IsAny<string>(), It.IsAny<DateTime>()))
.Callback<string, string, string, DateTime?>((p, k, v, e) =>
{
if (!v.Equals(token, StringComparison.InvariantCultureIgnoreCase))
throw new Exception($"RedisClient.Set expected value {token} but received {v}");
if (!e.Value.Equals(expires))
throw new Exception($"RedisClient.Set expected expires {expires} but received {e}");
});
我不得不在回调中使用异常这一事实似乎有点笨拙,因此我想知道是否有更好的方法来使用模拟来验证这一点。
这里是被测代码示例,实际被测方法调用的私有方法
string StoreClientDetailsInRedisAndReturnToken(string clientId, string clientName)
{
string token = Guid.NewGuid().ToString("N");
string data = $"{clientId},{clientName}";
DateTime expires = DateTime.UtcNow.AddDays(AdminSettings.Current.ExpiryDays);
RedisClient.Set(KeyPrefix, token, data, expires);
RedisClient.Set(KeyPrefix, clientId, token, expires);
return token;
}
令牌包含在通过电子邮件发送的激活 link 中,因此我们需要能够使用令牌检索数据。第二个 Redis 集允许我们还使用 clientId 检索令牌,以确认请求仍在等待处理。即 link 尚未被点击和处理,Redis 条目尚未过期并被删除。
我相信您会想建议其他方法来编写被测代码,但写这篇文章时我已经可以想象其他方法来做到这一点。从这个问题中我真正想知道的是 Moq 是否允许验证以某种方式相关的两种方法都以特定顺序调用。
这就是我测试此方法的方式。
这一切都相当简单,并且断言您在代码中指定的所有行为。
[TestMethod]
public void StoreClientDetailsInRedisAndReturnToken_SetsTwoValuesInRedis_ReturnsGuid()
{
var data = new List<dynamic>()
var redis = new Mock<IRedisClient>();
redis.Setup(x => x.Set(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DateTime>()).Callback((string kp, string token, string data, DateTime exp) => data.Add(new {KeyPrefix = kp, Key = token, Data = data, Expiry = exp);
var target = new MyClass(redis.Object);
var result = target.StoreClientDetailsInRedisAndReturnToken("funny", "banana");
new Guid(result); // do nothing, will throw if result is not parseable as Guid
Assert.AreEqual(2, data.Count())
Assert.AreEqual(KeyPrefix, data.FirstOrDefault().KeyPrefix);
Assert.AreEqual(result, data.FirstOrDefault().Key);
Assert.AreEqual("funny,banana", data.FirstOrDefault().Data);
// here if you have enterprise edition of visual studio you can use microsoft fakes to actually test it, or otherwise
Assert.IsTrue(((DateTime)data.FirstOrDefault().Expiry) > DateTime.UtcNow.AddDays(AdminSettings.Current.ExpiryDays).AddMinutes(-1));
Assert.AreEqual(KeyPrefix, data.LastOrDefault().KeyPrefix);
Assert.AreEqual("funny", data.LastOrDefault().Key);
Assert.AreEqual(result, data.LastOrDefault().Data);
Assert.IsTrue(((DateTime)data.LastOrDefault().Expiry) > DateTime.UtcNow.AddDays(AdminSettings.Current.ExpiryDays).AddMinutes(-1));
}
我想确保我正在测试的方法对某些参数使用相同值的模拟方法进行两次调用。这些值是在被测试的方法内部生成的,因此在设置测试时是未知的。在这种情况下,被测试的方法是在 Redis 中存储两个相关的项目,但这与问题无关;我问的是嘲笑。我想确认我所做的方式是最好的方式,它确实有效。也许我错过了 Moq 的其他一些功能可以让这件事以更好的方式完成。
这就是我的。
IRedisClient _redisClientMock = new Mock<IRedisClient>(MockBehavior.Strict);
string token = null;
DateTime expires = default(DateTime);
_redisClientMock
.Setup(x => x.Set(KEY_PREFIX, It.IsAny<string>(), $"{TEST_ID},{TEST_NAME}", It.IsAny<DateTime>()))
.Callback<string, string, string, DateTime?>((p, k, v, e) =>
{
token = k;
expires = e.Value;
});
_redisClientMock
.Setup(x => x.Set(KEY_PREFIX, TEST_ID, It.IsAny<string>(), It.IsAny<DateTime>()))
.Callback<string, string, string, DateTime?>((p, k, v, e) =>
{
if (!v.Equals(token, StringComparison.InvariantCultureIgnoreCase))
throw new Exception($"RedisClient.Set expected value {token} but received {v}");
if (!e.Value.Equals(expires))
throw new Exception($"RedisClient.Set expected expires {expires} but received {e}");
});
我不得不在回调中使用异常这一事实似乎有点笨拙,因此我想知道是否有更好的方法来使用模拟来验证这一点。
这里是被测代码示例,实际被测方法调用的私有方法
string StoreClientDetailsInRedisAndReturnToken(string clientId, string clientName)
{
string token = Guid.NewGuid().ToString("N");
string data = $"{clientId},{clientName}";
DateTime expires = DateTime.UtcNow.AddDays(AdminSettings.Current.ExpiryDays);
RedisClient.Set(KeyPrefix, token, data, expires);
RedisClient.Set(KeyPrefix, clientId, token, expires);
return token;
}
令牌包含在通过电子邮件发送的激活 link 中,因此我们需要能够使用令牌检索数据。第二个 Redis 集允许我们还使用 clientId 检索令牌,以确认请求仍在等待处理。即 link 尚未被点击和处理,Redis 条目尚未过期并被删除。
我相信您会想建议其他方法来编写被测代码,但写这篇文章时我已经可以想象其他方法来做到这一点。从这个问题中我真正想知道的是 Moq 是否允许验证以某种方式相关的两种方法都以特定顺序调用。
这就是我测试此方法的方式。 这一切都相当简单,并且断言您在代码中指定的所有行为。
[TestMethod]
public void StoreClientDetailsInRedisAndReturnToken_SetsTwoValuesInRedis_ReturnsGuid()
{
var data = new List<dynamic>()
var redis = new Mock<IRedisClient>();
redis.Setup(x => x.Set(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DateTime>()).Callback((string kp, string token, string data, DateTime exp) => data.Add(new {KeyPrefix = kp, Key = token, Data = data, Expiry = exp);
var target = new MyClass(redis.Object);
var result = target.StoreClientDetailsInRedisAndReturnToken("funny", "banana");
new Guid(result); // do nothing, will throw if result is not parseable as Guid
Assert.AreEqual(2, data.Count())
Assert.AreEqual(KeyPrefix, data.FirstOrDefault().KeyPrefix);
Assert.AreEqual(result, data.FirstOrDefault().Key);
Assert.AreEqual("funny,banana", data.FirstOrDefault().Data);
// here if you have enterprise edition of visual studio you can use microsoft fakes to actually test it, or otherwise
Assert.IsTrue(((DateTime)data.FirstOrDefault().Expiry) > DateTime.UtcNow.AddDays(AdminSettings.Current.ExpiryDays).AddMinutes(-1));
Assert.AreEqual(KeyPrefix, data.LastOrDefault().KeyPrefix);
Assert.AreEqual("funny", data.LastOrDefault().Key);
Assert.AreEqual(result, data.LastOrDefault().Data);
Assert.IsTrue(((DateTime)data.LastOrDefault().Expiry) > DateTime.UtcNow.AddDays(AdminSettings.Current.ExpiryDays).AddMinutes(-1));
}