Как правилно да тествам API контролер, който връща json за ненулев отговор?

Имам този тестов метод за тестване на API контролер, който връща JSON низ за ненулев отговор.

[TestClass]
public class TransactionsTests
{
    [TestMethod]
    public void ColorPerformance_GetChartData_NotNullResponse_Test()
    {
        // Arrange
        string quality = null, cars = null, year = "2015";

        var listColorPerformanceChartData = new List<ColorPerformanceChartData>();
        var mockRepository = new Mock<IColorPerformanceRepository>();
        mockRepository.Setup(x => x.GetChartData(quality, cars, year))
            .Returns(listColorPerformanceChartData);

        var controller = new ColorPerformanceController(mockRepository.Object);

        // Act
        IHttpActionResult actionResult = controller.GetChartData(quality, cars, year);
        var contentResult = actionResult as OkNegotiatedContentResult<object>;

        // Assert
        Assert.IsNotNull(contentResult);
        Assert.IsNotNull(contentResult.Content);
    }
}

Този тест преминава, тъй като contentResult не е нула. Не съм сигурен обаче, че тестът е написан правилно поради следните причини:

  1. contentResult.Content има празни данни, тъй като няма данни, върнати от метода _repository.GetChartData(), но не е празен, защото все още конструираният json е както следва:

{ categories = {int[0]}, series = { name = "Number of colors", data = {double[0]} } }

  1. contentResult.ContentNegotiator, contentResult.Formatter и contentResult.Request всички хвърлят изключение от InvalidOperationException със съобщението HttpControllerContext.Configuration must not be null. Не знам защо се случва това.

API контролерът:

public class ColorPerformanceController : ApiController
{
    private IColorPerformanceRepository _repository;
    public ColorPerformanceController(IColorPerformanceRepository repository)
    {
        _repository = repository;
    }
    public IHttpActionResult GetChartData(string quality, string cars, string year)
    {
        try 
        {
            var data = ProcessData(quality, cars, year);
            return Ok(data);
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }
    private object ProcessData(string quality, string cars, string year)
    {
        var data = _repository.GetChartData(quality, cars, year);
        return new {
            categories = data.Select(d => d.Id).ToArray(),
            series = new[] { new { name = "Number of colors", data = data.Select(d => d.CumulativePercentage).ToArray() }}
        };
    }
}

IColorPerformanceRepository:

public interface IColorPerformanceRepository
{
    IEnumerable<ColorPerformanceChartData> GetChartData(string quality, string cars, string year);
}

Обектът, върнат от изпълнението на хранилището:

public class ColorPerformanceChartData
{
    private double _cumulativePercentage;
    public double CumulativePercentage {
        get { return Math.Round(_cumulativePercentage, 2); }
        set { _cumulativePercentage = value; }
    }
    public int Id { get; set; }
}

Какво пропускам или правя погрешно тук?


person Animesh    schedule 10.07.2015    source източник
comment
опитахте ли да дебъгвате теста, за да видите къде извежда грешката?   -  person DLeh    schedule 10.07.2015
comment
Да, направих отстраняване на грешки в теста и наистина никъде не се провали. Както споменахме, тестът преминава успешно. Просто не виждам никакви данни да идват от _repository.GetChartData() повикването. Това ли е защото просто се подигравам на поведението или трябва да получа данни?   -  person Animesh    schedule 10.07.2015
comment
добре, тогава е малко неясно какво питаш, защото споменаваш, че ...and contentResult.Request all are throwing an exception of InvalidOperationException...   -  person DLeh    schedule 10.07.2015
comment
Наистина не бях сигурен дали това, което имам, е достатъчен тест поради двете причини, които споменах. Очаквах някои данни от метода _repository.GetChartData(), но след това осъзнах, че MockRepository всъщност не извиква действителния метод на хранилището.   -  person Animesh    schedule 10.07.2015


Отговори (1)


Най-добрата практика е да избягвате използването на анонимен тип в този случай:

private object ProcessData(string quality, string cars, string year)
    {
        var data = _repository.GetChartData(quality, cars, year);
        return new {
            categories = data.Select(d => d.Id).ToArray(),
            series = new[] { new { name = "Number of colors", data = data.Select(d => d.CumulativePercentage).ToArray() }}
        };
    }

Опитайте да дефинирате клас за него, така че да можете да десериализирате низа и да проверите всяко свойство:

        // Act
        IHttpActionResult actionResult = controller.GetChartData(quality, cars, year);
        //Notice I use YourClass instead of object here.
        var contentResult = actionResult as OkNegotiatedContentResult<YourClass>;

        // Assert
        Assert.IsNotNull(contentResult);   
        Assert.IsNotNull(contentResult.Content);   
        //Assert all properties of contentResult.Content like categories, series,..

Що се отнася до изключението, опитайте:

var controller = new ColorPerformanceController(mockRepository.Object);
//Add these 2 lines
 controller.Request = new HttpRequestMessage();
 controller.Configuration = new HttpConfiguration();

От http://www.asp.net/web-api/overview/testing-and-debugging/unit-testing-controllers-in-web-api

person Khanh TO    schedule 10.07.2015
comment
Разбирам, че е по-добре да избягвате анонимни типове, но причината да изберете анонимен обект беше да създадете JSON низ в специфичен формат, който библиотеката с диаграми на интерфейса приема. - person Animesh; 10.07.2015
comment
@Animesh: Не разбирам защо тази причина ви ограничава да използвате анонимни типове. - person Khanh TO; 10.07.2015
comment
Няма абсолютна причина, това беше нещо, създадено бързо и мръсно въз основа на променящите се изисквания. Но освен огромните подобрения в четливостта и поддръжката чрез връщане на строго типизиран клас, любопитен съм да знам дали има някакви други предимства. Бих добавил, че рано или късно може да се опитам да създам строго типизирани класове. - person Animesh; 10.07.2015
comment
@Animesh: Мисля, че това са основните предимства. Мисля, че друго предимство е в този случай, че можете да получите достъп до неговите свойства по време на компилация - person Khanh TO; 10.07.2015