如何在单元测试中模拟字符串响应?

时间:2011-04-19 03:31:33

标签: c# unit-testing nunit

这是我到目前为止测试中的内容:

[TestFixture]
public class IndividualMovieTests
{
    [Test]
    public void WebClient_Should_Download_From_Correct_Endpoint()
    {
        const string correctEndpoint = "http://api.rottentomatoes.com/api/public/v1.0/movies/{movie-id}.json?apikey={your-api-key}";
        ApiEndpoints.Endpoints["IndividualMovie"].ShouldEqual(correctEndpoint);
    }

    [Test]
    public void Movie_Information_Is_Loaded_Correctly()
    {
        Tomato tomato = new Tomato("t4qpkcsek5h6vgbsy8k4etxdd");
        var movie = tomato.FindMovieById(9818);
        movie.Title.ShouldEqual("Gone With The Wind");
    }
}

我的FIndMovieById方法联机并获取JSON结果,这意味着它打破了背后单元测试的原则。我有一种感觉,我必须模拟这个字符串响应,但我真的不知道如何处理这个。

如何接近这个特定的单元测试?

3 个答案:

答案 0 :(得分:3)

在您的第二个[Test]中,我建议您不要关注FindMovieById方法的特定返回值,除非您真的想要测试您的给定输入应始终导致“飘” ”。您所拥有的测试似乎是一个非常具体的测试用例,其中特定的输入数字会产生特定的输出,这在针对您的实际数据库运行时可能会也可能不会发生变化。此外,由于您不打算对实际的Web服务进行测试,因此进行此类验证基本上是自助服务 - 您实际上并未测试任何内容。相反,专注于测试Tomato类如何处理参数的验证(如果有的话),并且Tomato类实际调用服务来获取返回值。而不是测试特定的输入和输出,测试类的行为,以便如果有人在将来更改它,测试应该中断以提醒他们他们可能已经破坏了工作功能。

例如,如果您有输入验证,则可以测试如果检测到无效输入,您的Tomato类会抛出异常。

假设您的Tomato类具有某种用于请求和检索结果的Web客户端功能,您可以插入实际Web代码的一些存根实现,或模拟实现,以确保Tomato实际上调用适当的Web客户端用于请求和处理响应的代码。

答案 1 :(得分:2)

首先,您可能不必模拟测试代码。例如,如果您只是测试可以将JSON反序列化为Movie对象,则可以通过在ParseJSON类上测试公共或内部Movie秒来实现。

但是,既然你在询问模拟,那么这里有一个快速概述你可以使用模拟编写这个测试的方法。如上所述,Movie_Information_Is_Loaded_Correctly()看起来像集成测试。要将其转换为单元测试,您可以模拟Tomato类所做的Web请求。一种方法是创建一个ITomatoWebRequester接口,并将其作为参数传递给构造函数中的Tomato类。然后,您可以模拟ITomatoWebRequester以返回您期望的Web响应,然后您可以测试Tomato类是否正确解析该响应。

代码看起来像这样:

public class Tomato
{
    private readonly ITomatoWebRequester _webRequester;
    public Tomato(string uniqueID, ITomatoWebRequester webRequester)
    {
        _webRequester = webRequester;
    }

    public Movie FindMovieById(int movieID)
    {
        var responseJSON = _webRequester.GetMovieJSONByID(movieID);
        //The next line is what we want to unit test
        return Movie.Parse(responseJSON); 
    }
}

public interface ITomatoWebRequester
{
    string GetMovieJSONByID(int movieID);
}

要进行测试,您可以使用像Moq这样的模拟框架来创建一个ITomatoWebRequester,它将返回您期望的结果。要使用Moq执行此操作,以下代码应该有效:

[Test]
public void Movie_Information_Is_Loaded_Correctly()
{
    var mockWebRequester = new Moq.Mock<ITomatoWebRequester>();
    var myJson = "enter json response you want to use to test with here";
    mockWebRequester.Setup(a => a.GetMovieJSONByID(It.IsAny<int>())
        .Returns(myJson);

    Tomato tomato = new Tomato("t4qpkcsek5h6vgbsy8k4etxdd", 
        mockWebRequester.Object);
    var movie = tomato.FindMovieById(9818);
    movie.Title.ShouldEqual("Gone With The Wind");
}

在这种情况下,关于模拟的一个很酷的事情就是你不必担心实际的ITomatoWebRequester跳过所有箍以返回它应该返回的JSON,你可以创建一个在你的测试中模拟正确返回你想要的东西。希望这个答案可以作为一个体面的嘲弄介绍。我肯定会建议阅读模拟框架,以便更好地了解该过程的工作原理。

答案 2 :(得分:1)

使用Rhino.Mocks库并在适当的地方调用Expectations。以下是模拟电影对象的示例。

using System;
using NUnit.Framework;
using Rhino.Mocks;
namespace ConsoleApplication1
{
    public class Tomato
    {
        public Tomato(string t4qpkcsek5h6vgbsy8k4etxdd)
        {
           //
        }

        public virtual Movie FindMovieById(int i)
        {
            return null;
        }
    }

    public class Movie
    {
        public string Title;

        public Movie( )
        {

        }

        public void FindMovieById(int i)
        {
            throw new NotImplementedException();
        }
    }

    [TestFixture]
    public class IndividualMovieTests
    {
        [Test]
        public void Movie_Information_Is_Loaded_Correctly()
        {

            //Create Mock.
            Tomato tomato = MockRepository.GenerateStub<Tomato>("t4qpkcsek5h6vgbsy8k4etxdd");

            //Put expectations.
            tomato.Expect(t=>t.FindMovieById(0)).IgnoreArguments().Return(new Movie(){Title ="Gone With The Wind"});

            //Test logic.
            Movie movie = tomato.FindMovieById(9818);

            //Do Assertions.
            Assert.AreEqual("Gone With The Wind", movie.Title);

            //Verify expectations.
            tomato.VerifyAllExpectations();
        }
    }
}