我应该如何使用Moq测试方法之间的依赖关系?

时间:2017-05-30 08:01:19

标签: c# unit-testing moq

我想确保我正在测试的方法使用相同的值对某些参数进行两次对mocked方法的调用。这些值是在被测试的方法内部生成的,因此在设置测试时不知道。在这种情况下,被测试的方法是在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;
}

令牌包含在电子邮件中发送的激活链接中,因此我们需要能够使用令牌检索数据。第二个Redis集允许我们使用clientId检索令牌,以确认请求仍处于挂起状态。即没有点击和处理链接,并且Redis条目尚未过期并被删除。

我确定你会想要建议其他方法来编写测试代码,但写下这个我已经可以想象其他方法了。我真正想从这个问题中得知的是,Moq是否允许人们验证以某种方式相关的两种方法都是按特定顺序调用的。

1 个答案:

答案 0 :(得分:1)

这就是我测试这种方法的方法。 对于您在代码中指定的所有行为,它都非常简单并且断言。

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