这是Moq的正确用法吗?

时间:2014-05-28 08:15:07

标签: c# unit-testing cookies moq

我有以下界面为我提供了一种使用cookie的方法:

/// <summary>
/// Provides an interface that makes it easy to work with cookies.
/// </summary>
public interface ICookies
{
    #region Properties

    /// <summary>
    /// Gets or sets the value of the <see cref="ICookies"/>.
    /// </summary>
    /// <param name="name">The name of the cookie.</param>
    /// <returns>A string that represents the value in this cookie.</returns>
    string this[string name] { get; set; }

    #endregion

    #region Methods

    /// <summary>
    /// Writes a new cookie.
    /// </summary>
    /// <param name="name">The name of the cookie.</param>
    /// <param name="expiration">The <see cref="DateTime"/> when this cookie expires.</param>
    /// <param name="value">The value that the cookie should have.</param>
    void Create(string name, string value, DateTime expiration);

    /// <summary>
    /// Checks wether a cookie with a specific name does exist.
    /// </summary>
    /// <param name="name">The name of the cookie.</param>
    /// <returns><see langword="true"/> if the cookie does exists, otherwise, <see langword="false"/>.</returns>
    bool DoesExist(string name);

    #endregion
}

我有以下经理从上面获取ICookie界面来创建cookie。该课程如下所示:

/// <summary>
///     Provides an easy way to work with cookies on the server.
/// </summary>
public static class CookieManager
{
    #region Methods

    /// <summary>
    ///     Writes a new cookie on the.
    /// </summary>
    /// <param name="cookies">The <see cref="ICookies" /> that is responsible for working with cookies.</param>
    /// <param name="name">The name of the cookie.</param>
    /// <param name="value">The value of the cookie.</param>
    public static void Write(ICookies cookies, string name, string value)
    {
        if (!Exists(cookies, name))
        {
            cookies.Create(name, value, DateTime.Now.AddYears(1));
        }

        cookies[name] = value;
    }

    /// <summary>
    ///     Reads a cookie.
    /// </summary>
    /// <param name="cookies">The <see cref="ICookies" /> that is responsible for working with cookies.</param>
    /// <param name="name">The name of the cookie to read.</param>
    /// <returns>The value of the cookie.</returns>
    /// <exception cref="KeyNotFoundException">The cookie is not existing.</exception>
    public static string Read(ICookies cookies, string name)
    {
        return cookies[name];
    }

    /// <summary>
    ///     Check if a cookie does exists.
    /// </summary>
    /// <param name="cookies">The <see cref="ICookies" /> that is responsible for working with cookies.</param>
    /// <param name="name">The name of the cookie.</param>
    /// <returns><see langword="true" /> when the cookie does exists, otherwise <see langword="false" />.</returns>
    public static bool Exists(ICookies cookies, string name)
    {
        return cookies.DoesExist(name);
    }

    #endregion
}

现在,我想编写一个单元测试来模拟cookie接口以确保它通过。

我想查看以下内容:

  • 当我调用CookieManager.Write时,应该写一个cookie,我应该能够使用CookieManager.Read函数读取它。正确的方法是什么,因为我已经尝试了所有方法(使用Verifyable,Setup,SetupGet,SetupSet)。

非常重要的是,我正在与Moq合作。

这就是我现在拥有的东西:

我有一个不变的第一个:

protected const string CookieReturnValue = "ReturnValue";

然后我嘲笑了ICookie:

protected override void Arrange()
   {
       cookies = new Mock<ICookies>();

       cookies.Setup(c => c.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DateTime>()));
       cookies.SetupGet(c => c[It.IsAny<string>()]).Returns(CookieReturnValue).Verifiable();
   }

然后执行测试:

protected override void Act()
{
    CookieManager.Write(cookies.Object, "MyCookie", "MyValue");
    CookieManager.Read(cookies.Object, "MyCookie");
}

最后,测试得到验证:

cookies.Verify();

现在,这个测试正在通过,但是我不太确定这是否是正确的测试方式,因为在我的模拟中,我返回了常量的CookieReturnValue&#39;。我应该能够将其设置为在&#39; cookies.Setup()&#39;中输入的值作为第二个参数。方法,其中第二个参数保存cookie的值。

这里的主要问题是,如果我没有写cookie,测试也会通过。我需要确保测试只是在我写一个cookie然后再次读取相同的cookie时才通过。

如果这是正确的测试方式,或者我应该调整哪些方式以确保它正常工作,那么可以吗?

1 个答案:

答案 0 :(得分:2)

你实际上有三次测试。

第一个测试CookieManager是否检查cookie是否存在,如果不存在,则调用Create,然后设置cookie:

var cookies = new Mock<ICookies>();

// there's no cookie
cookies.Setup(c => c.DoesExist(It.IsAny<string>())).Returns(false);

CookieManager.Write(cookies.Object, "MyCookie", "MyValue");

// check if create was called with the right parameters
cookies.Verify(c => c.Create("MyCookie", "MyValue", It.IsAny<DateTime>()));
// check if the cookie was set
cookies.VerifySet(mock => mock["MyCookie"] = "MyValue");

第二个测试CookieManager是否检查cookie是否存在,如果存在,请不要调用Create,然后设置cookie:

// there's a cookie
cookies.Setup(c => c.DoesExist(It.IsAny<string>())).Returns(true);

CookieManager.Write(cookies.Object, "MyCookie", "MyValue");

// check if create was NOT called
cookies.Verify(c => c.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DateTime>()), Times.Never);
// check if the cookie was set
cookies.VerifySet(mock => mock["MyCookie"] = "MyValue");

第三个测试CookieManager是否尝试读取cookie:

var cookies = new Mock<ICookies>();

CookieManager.Read(cookies.Object, "MyCookie");

// check if the indexer was used with the right key
cookies.Verify(c => c["MyCookie"]);

注意:简单地调用cookies.Verify()本身不会做任何事情。您必须像我在我的示例中一样向Verify提供代理,或致电VerifyAll验证所有SetUps

另外,你可以通过例如更严格的方式使这些测试更严格。使用Times.OnceMockBehavior.Strict

例如,第一个测试也可以写成:

var cookies = new Mock<ICookies>(MockBehavior.Strict);

// there's no cookie
cookies.Setup(c => c.DoesExist("MyCookie")).Returns(false);
// so one has to be created with the right parameters
cookies.Setup(c => c.Create("MyCookie", "MyValue", It.IsAny<DateTime>()));
cookies.SetupSet(mock => mock["MyCookie"] = "MyValue");

CookieManager.Write(cookies.Object, "MyCookie", "MyValue");

cookies.VerifyAll();

此外,我不知道为什么CookieManager在使用cookies.Create(...)创建Cookie值后再次设置


此外,没有必要设置模拟以返回常量CookieReturnValue,然后检查该常量是否实际返回我的模拟。这不会测试CookieManager,而是模拟对象本身,这是毫无意义的。