简体   繁体   中英

Visual Studio 2017 Code Coverage Reports Partial Coverage on Async Methods

I'm working on a small unit test for a simple asp.net core middleware and trying to work out if it's possible to get 100% coverage on this very basic scenario. I'm using Visual Studio 2017 > "Analyze Code Coverage", xUnit and Moq for completeness. On my async methods (one illustrated below) code analysis is reporting only partial coverage. Is there a way to get these fully covered?

// Sample Middleware

internal sealed partial class JsonExceptionMiddleware
{
    private const string DefaultErrorMessage = "A server error occurred.";
    private readonly RequestDelegate _next;
    private readonly ILogger<JsonExceptionMiddleware> _logger;


    public JsonExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _logger = loggerFactory?.CreateLogger<JsonExceptionMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));

        IncludeExceptionMessage = hostingEnvironment.IsDevelopment();
        IncludeExceptionStackTrace = hostingEnvironment.IsDevelopment();
    }


    /// <summary>
    /// Gets or sets whether the <see cref="Exception.StackTrace"/> should be included in the response message.
    /// </summary>
    public bool IncludeExceptionStackTrace { get; set; }

    /// <summary>
    /// Gets or sets whether the <see cref="Exception.Message"/> should be included in the response message.
    /// </summary>
    public bool IncludeExceptionMessage { get; set; }

    /// <summary>
    /// Implements the <see cref="RequestDelegate"/> so this class can be used as middleware.
    /// </summary>
    /// <param name="context">The current <see cref="HttpContext"/>.</param>
    /// <returns>A <see cref="Task"/> that completes when the error message is flush to the HTTP response.</returns>
    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            if (context.Response.HasStarted) throw;

            context.Response.Clear();
            context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            context.Response.ContentType = ApiConstants.Http.JsonContentType;

            ApiError error = BuildError(ex);

            await context.Response.WriteAsync(JsonConvert.SerializeObject(error, new JsonSerializerSettings(){ NullValueHandling = NullValueHandling.Ignore}));
        }
    }

    private ApiError BuildError(Exception ex)
    {
        string message = DefaultErrorMessage;
        string detail = null;
        string stack = null;

        if (IncludeExceptionMessage)
            detail = ex.Message;

        if (IncludeExceptionStackTrace)
            stack = ex.StackTrace;

        var error = new ApiError(message, detail, stack);
        return error;
    }
}

Blue=covered, Yellow=partially covered, Red=not covered

在此处输入图片说明

在此处输入图片说明

// Sample Unit Test

    [Fact]
    public async Task SampleUnit()
    {
        // arrange

        var environment = new Mock<IHostingEnvironment>();
        environment
            .SetupGet(x => x.EnvironmentName)
            .Returns(EnvironmentName.Development);

        var response = new Mock<HttpResponse>();
        response
            .Setup(x => x.HasStarted)
            .Returns(true);

        var httpContext = new Mock<HttpContext>();
        httpContext
            .SetupGet(x => x.Response)
            .Returns(response.Object);

        var loggerFactory = new Mock<LoggerFactory>();

        var jsonExceptionMiddleware = new JsonExceptionMiddleware((innerHttpContext) => throw new Exception(SampleExceptionDetail), loggerFactory.Object, environment.Object);

        // act & assert

        await Assert.ThrowsAsync<Exception>(async () => await jsonExceptionMiddleware.Invoke(httpContext.Object).ConfigureAwait(false));
    }

From the looks of the covered code, the test(s) throw on the await and only flows through the catch block.

Allow the await to flow to completion by not throwing an exception in the request delegate. Using the sample test provided, you would need to initialize the middleware like this

//...

var jsonExceptionMiddleware = new JsonExceptionMiddleware((context) => Task.CompletedTask, 
    loggerFactory.Object, environment.Object);

//...

For the other uncovered code, you just have to make sure an error is thrown on the await like it is now, but make sure that context.Response.HasStarted is true .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM