namespace WebApiDemo.Models
{
    public class Error
    {
        public string Code { get; set; }
        public string Message { get; set; }
        public IEnumerable<string> Errors { get; set; }
    }
}

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using WebApiDemo.Models;

namespace WebApiDemo.Middlewares
{
    public class CustomExceptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<CustomExceptionMiddleware> _logger;

        public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

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

        private async Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            var response = context.Response;
            var message = "Unhandled error";
            var code = "00009";
            var errors = new List<string>();

            response.ContentType = "application/json";
            response.StatusCode = (int)HttpStatusCode.InternalServerError;

            // log de l’erreur
            _logger.LogError(exception, exception.Message);
            
            // Réponse
            await response.WriteAsync(JsonConvert.SerializeObject(new Error
            {
                Code = code,
                Message = message,
                Errors = errors
            }));
        }
    }
}

//
// Summary:
//     A function that can process an HTTP request.
//
// Parameters:
//   context:
//     The Microsoft.AspNetCore.Http.HttpContext for the request.
//
// Returns:
//     A task that represents the completion of request processing.
 public delegate Task RequestDelegate(HttpContext context);

using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using NSubstitute;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using WebApiDemo.Middlewares;
using WebApiDemo.Models;
using Xunit;

namespace UnitTestsDemo
{
    public class CustomExceptionMiddlewareTests
    {
        [Fact]
        public async Task WhenAGenericExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToProperErrorResponseAndLoggerCalled()
        {
            // Test ici
        }
    }
}

[Fact]
public async Task WhenAGenericExceptionIsRaised_CustomExceptionMiddlewareShouldHandleItToProperErrorResponseAndLoggerCalled()
{
// Arrange
       var loggerMock = Substitute.For<ILogger<CustomExceptionMiddleware>>();
       RequestDelegate requestDelegate = ((innerHttpContext) =>
       {
       	throw new Exception("Oooops error!");
       };
       var middleware = new CustomExceptionMiddleware(requestDelegate, loggerMock);

       var context = new DefaultHttpContext();

       context.Response.Body = new MemoryStream();	
}

//Act
await middleware.Invoke(context);

//Assert
// repositionnement au début
context.Response.Body.Seek(0, SeekOrigin.Begin);

// Lecture de la réponse
var reader = new StreamReader(context.Response.Body);
var streamText = reader.ReadToEnd();
var objResponse = JsonConvert.DeserializeObject<Error>(streamText);

objResponse
 .Should()
 .BeEquivalentTo(new { Message = "Unhandled error", Errors = new List<string>() , Code = "00009" });

 context.Response.StatusCode
 .Should()
 .Be((int)HttpStatusCode.InternalServerError);

context.Response.ContentType
.Should()
 .Be("application/json");

 loggerMock.Received(1);

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using System;
using System.Net.Http;
using WebApiDemo;

namespace IntegrationTestsDemo
{
    public class TestServerFixture : IDisposable
    {
        private readonly TestServer _testServer;
        public HttpClient Client { get; }

        public TestServerFixture()
        {
            var builder = new WebHostBuilder()
                   .UseEnvironment("Development")
                   .UseStartup<Startup>();

            _testServer = new TestServer(builder);
            Client = _testServer.CreateClient();

        }

        public void Dispose()
        {
            Client.Dispose();
            _testServer.Dispose();
        }
    }
}

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace WebApiDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DemoExceptionController : ControllerBase
    {
        public DemoExceptionController()
        {
        }

        [HttpGet]
        public IEnumerable<string> Get()
        {
            throw new Exception("bouhhhh quelle affreuse erreur!");
            return new string[] { "value1", "value2" };
        }

    }
}

using FluentAssertions;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using WebApiDemo.Models;
using Xunit;

namespace IntegrationTestsDemo
{
    public class DemoExceptionControllerTests
    {
        [Fact]
        public async Task WhenGetMethodRaiseAnException_WebAPIShouldHandleItandAnswerAProperErrorObjectAndStatusError500()
        {
            //Arrange
            using (TestServerFixture fixture = new TestServerFixture())
            {
                // Act
                var response = await fixture.Client.GetAsync("/api/DemoException");

                // Assert
                var responseData = await response
                .Content
                .ReadAsAsync<Error>();

                responseData
                .Should()
                .BeEquivalentTo(new Error { Message = "Unhandled error", Errors = new List<string>(), Code = "00009" });

                response.StatusCode
                .Should()
                .Be((int)HttpStatusCode.InternalServerError);

                response
                .Content
                .Headers
                .ContentType
                .MediaType
                .Should()
                .Be("application/json");
            }

        }

    }
}

var responseData = await response
                .Content
                .ReadAsAsync<Error>();

                responseData
                .Should()
                .BeEquivalentTo(new Error { Message = "Unhandled error", Errors = new List<string>(), Code = "00009" });