Как правильно протестировать 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