简体   繁体   English

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

[英]How should I test dependencies between methods using Moq?

I want to ensure that the method I am testing makes two calls to a mocked method using the same values for certain arguments. 我想确保正在测试的方法使用某些参数的相同值对模拟方法进行两次调用。 These values are generated inside the method being tested so are not known when the test is set up. 这些值是在要测试的方法内部生成的,因此在设置测试时未知。 In this case the method being tested is storing two related items in Redis, but this is not relevant to the question; 在这种情况下,被测试的方法是在Redis中存储两个相关的项目,但这与问题无关。 it's the mocking that I'm asking about. 这是我要问的嘲笑。 I want to confirm that the way I've done it, which does work, is the best way to do it. 我想确认的是,完成工作的方式是执行此操作的最佳方法。 Perhaps there is some other feature of Moq that I have missed that could let this be done in a better way. 可能我还没有想过Moq的其他功能,可以让它以更好的方式完成。

This is what I have. 这就是我所拥有的。

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

The fact that I've had to use exceptions in a callback seems a bit clunky, hence my wondering if there is a better way to verify this using mocking. 我不得不在回调中使用异常的事实似乎有点笨拙,因此我想知道是否有更好的方法可以通过模拟来验证这一点。

Here is an example of the code under test, a private method called by the method actually being tested. 这是被测试代码的示例,这是由实际测试的方法调用的私有方法。

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

The token is included in an activation link sent out in an email, so we need to be able to retrieve the data using the token. 令牌包含在通过电子邮件发送的激活链接中,因此我们需要能够使用令牌检索数据。 The second Redis set is allowing us to also retrieve the token using the clientId, in order to confirm that the request is still pending. 第二个Redis集允许我们也使用clientId检索令牌,以确认请求仍在等待处理中。 ie the link has not been clicked and processed and the Redis entries have not expired and been removed. 即,该链接尚未被单击和处理,并且Redis条目尚未过期并已被删除。

I'm sure that you will want to suggest other ways to write the code under test but writing this I can already picture other ways to do it. 我确定您会建议其他方法来编写受测代码,但是编写此代码后,我已经可以想到其他方法来编写它。 What I would really like to know from this question is whether or not Moq allows one to verify that two methods, related in some way, are both called in a particular sequence. 从这个问题中我真正想知道的是,Moq是否允许一个人验证以某种方式关联的两种方法都以特定顺序调用。

This is how i would test this method. 这就是我将如何测试此方法。 It is all fairly straightforward and asserts on all the behaviours you have specified in your code. 这一切都非常简单明了,并且可以对您在代码中指定的所有行为进行断言。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM