Тестирование response.WriteAsync () в пользовательском промежуточном ПО - PullRequest
2 голосов
/ 30 июня 2019

У меня есть ASP.NET Core API, для которого я написал специальное промежуточное программное обеспечение, чтобы я мог обрабатывать исключения и записывать журналы в одном месте.Промежуточное программное обеспечение работает как требуется при отладке через Kestrel и отправке запроса из браузера или почтальона, однако в моем тесте тело ответа всегда представляет собой нулевой поток.

Ниже приведен класс промежуточного программного обеспечения и тест, который я написал,context.Response.WriteAsync (результат), кажется, не очищает поток по какой-то причине, но я не знаю почему.Кто-нибудь может объяснить?

using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using System.IO;

namespace APP.API.Middleware
{
    public class ExceptionHandler
    {
        private readonly RequestDelegate request;
        private readonly ILogger logger;

        public ExceptionHandler(RequestDelegate request, ILogger<ExceptionHandler> logger)
        {
            this.request = request;
            this.logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await request(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private Task HandleExceptionAsync(HttpContext context, Exception ex)
        {
            HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
            logger.LogError(ex, "Fatal exception");

            var result = JsonConvert.SerializeObject(new { error = ex.Message });
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)statusCode;

            return context.Response.WriteAsync(result);
        }
    }
}

using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace APP.Testing.Middleware
{
    [TestClass]
    public class ExceptionHandler
    {
        [TestMethod]
        public void HandleException()
        {
            var exceptionHandler = new API.Middleware.ExceptionHandler((innerHttpContext) =>
            {
                throw new System.Exception("Test exception");
            }, new NullLogger<API.Middleware.ExceptionHandler>());

            var context = new DefaultHttpContext();

            exceptionHandler.Invoke(context).Wait();

            context.Response.Body.Seek(0, SeekOrigin.Begin);
            var reader = new StreamReader(context.Response.Body);
            var text = reader.ReadToEnd();

        }
    }
}

1 Ответ

3 голосов
/ 30 июня 2019

Добро пожаловать в переполнение стека!

Ваше тело ответа пусто, потому что вы пишете в NullStream (не путать со значением null).

"Поток без резервного хранилища. Используйте Null, чтобы перенаправить вывод в поток, который не будет потреблять ресурсы операционной системы. Когда методы Stream, обеспечивающие запись, вызываются на Null, вызов просто возвращается, и данные отсутствуют. записано. Null также реализует метод Read, который возвращает ноль без чтения данных. " - Документы

Значение по умолчанию Body, свойство HttpResponse точно NullStream. В реальном сценарии, когда приходит HTTP-запрос, NullStream заменяется на HttpResponseStream. Вы не сможете использовать его самостоятельно, поскольку его уровень доступности установлен на internal.

Решение

Поскольку модульное тестирование только моделирует реальный сценарий, вы можете просто заменить NullStream на любой тип потока, который вы хотите, например, MemoryStream:

var exceptionHandler = new ExceptionHandler((innerHttpContext) =>
{
    throw new Exception("Test exception");
}, new NullLogger<ExceptionHandler>());

var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream(); // <== Replace the NullStream

await exceptionHandler.Invoke(context);

context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var text = reader.ReadToEnd();

Не забудьте добавить некоторые утверждения в конце ваших модульных тестов. В конце концов, вы хотите выполнить некоторые проверки, верно?

Assert.IsFalse(string.IsNullOrEmpty(text));

РЕДАКТИРОВАТЬ # 1

Как отметил @nkosi, если у вас нет веских причин, вы всегда должны вызывать асинхронные методы с ключевым словом await:

await exceptionHandler.Invoke(context);

и пометьте определение метода с помощью async и верните ему Task:

public async Task HandleException()

Таким образом, вы избегаете тупиков .

Что-то, на что также стоит обратить внимание (но не на необходимость), это соглашение об именах для тестирования классов. Очевидно, вы можете называть его так, как вам нравится, но имейте в виду, что когда у вашего класса тестирования будет то же имя, что и у класса, который вы хотите протестировать, вы получите ненужную двусмысленность имен. Конечно, вы можете написать полное имя с пространством имен (как вы сделали), но с моей ленивой натурой, это слишком много, поэтому я использую другое имя для тестирования класса, например ExceptionHandlerTests.

...