{"id":1523,"date":"2020-06-22T10:00:12","date_gmt":"2020-06-22T08:00:12","guid":{"rendered":"http:\/\/tpodolak.com\/blog\/?p=1523"},"modified":"2020-06-21T15:59:45","modified_gmt":"2020-06-21T13:59:45","slug":"asp-net-core-adding-controllers-directly-integration-tests","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2020\/06\/22\/asp-net-core-adding-controllers-directly-integration-tests\/","title":{"rendered":"ASP.NET Core &#8211; adding controllers directly from integration tests"},"content":{"rendered":"<h3>1. Introduction<\/h3>\n<p>From time to time it might happen, that you need to test certain parts of your <i>ASP.NET Core<\/i> configuration without hitting publicly visible business-related controllers. For instance, in my case, I wanted to make sure that my <i>ASP.NET Core API<\/i> behavior is consistent with the original <i>API<\/i> written in <i>Nancy<\/i>. As you might expect there are quite a lot of differences between both of the frameworks, so let&#8217;s focus on testing one thing, namely non-nullable reference type handling. Long story short, in order to have the same behavior in <i>ASP.NET Core<\/i> as in <i>Nancy<\/i> I had to add the following line of configuration<\/p>\n<pre lang=csharp>\r\n\/\/ This method gets called by the runtime. Use this method to add services to the container.\r\npublic void ConfigureServices(IServiceCollection services)\r\n{\r\n    services.AddControllers(opts => opts.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true)\r\n        .AddNewtonsoftJson();\r\n}\r\n<\/pre>\n<p>This one prevents <i>ASP.NET Core<\/i> from marking non-nullable reference type properties in request as required. Having that configuration ready, I wanted to test it with an end to end test. Because there was no business-related logic yet, I needed to figure out the way of adding <i>API<\/i> controllers to my application directly from the integration tests. Here is how you can achieve that.<\/p>\n<h3>2. Accessing ApplicationPartManager from integration tests<\/h3>\n<p><i>ASP.NET Core<\/i> is able to compose your <i>API<\/i> from different parts thanks to <i>ApplicationPartManager<\/i>. By default, you don&#8217;t use it directly but rather with <i>IMvcBuilder AddApplicationPart<\/i> extension method while setting up your application<\/p>\n<pre lang=csharp>\r\n\/\/ This method gets called by the runtime. Use this method to add services to the container.\r\npublic void ConfigureServices(IServiceCollection services)\r\n{\r\n    services.AddControllers()\r\n        .AddApplicationPart(typeof(SomeClass).Assembly)\r\n        .AddNewtonsoftJson();\r\n}\r\n<\/pre>\n<p>However, there is no easy way of getting <i>IMvcBuidler<\/i> from the integration tests(at least I didn&#8217;t find a proper way of doing that without tampering too much with the original application pipeline) so we need to access <i>ApplicationPartManager<\/i> directly. Inspecting the source code of the framework you will find that <i>ApplicationPartManager<\/i> can be retrieved directly from <i>IServiceCollection<\/i> with following <a href=\"https:\/\/github.com\/dotnet\/aspnetcore\/blob\/3.0\/src\/Mvc\/Mvc.Core\/src\/DependencyInjection\/MvcCoreServiceCollectionExtensions.cs#L74\">code<\/a><\/p>\n<pre lang=csharp>\r\nprivate static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)\r\n{\r\n    var manager = GetServiceFromCollection<ApplicationPartManager>(services);\r\n    if (manager == null)\r\n    {\r\n        manager = new ApplicationPartManager();\r\n\r\n        var environment = GetServiceFromCollection<IWebHostEnvironment>(services);\r\n        var entryAssemblyName = environment?.ApplicationName;\r\n        if (string.IsNullOrEmpty(entryAssemblyName))\r\n        {\r\n            return manager;\r\n        }\r\n\r\n        manager.PopulateDefaultParts(entryAssemblyName);\r\n    }\r\n\r\n    return manager;\r\n}\r\n\r\nprivate static T GetServiceFromCollection<T>(IServiceCollection services)\r\n{\r\n    return (T)services\r\n        .LastOrDefault(d => d.ServiceType == typeof(T))\r\n        ?.ImplementationInstance;\r\n}\r\n<\/pre>\n<p>Applying similar code to the <i>WebApplicationFactory<\/i> will allow us to add controllers directly from tests<\/p>\n<pre lang=csharp>\r\n[Fact]\r\npublic async Task DoesNotReturnBadRequest_WhenNonNullableTypeHasNoValue_FirstAttempt()\r\n{\r\n    var webApplicationFactory = new WebApplicationFactory<Startup>()\r\n        .WithWebHostBuilder(\r\n            builder => builder.ConfigureServices(services =>\r\n            {\r\n                var partManager = (ApplicationPartManager) services\r\n                    .Last(descriptor => descriptor.ServiceType == typeof(ApplicationPartManager))\r\n                    .ImplementationInstance;\r\n\r\n                partManager.ApplicationParts.Add(new AssemblyPart(GetType().Assembly));\r\n            }));\r\n\r\n    var expectedResponse = new Request\r\n    {\r\n        Id = \"1\"\r\n    };\r\n\r\n    var httpClient = webApplicationFactory.CreateClient();\r\n    var httpResponseMessage = await httpClient.GetAsync(\"test?id=1\");\r\n\r\n    httpResponseMessage.IsSuccessStatusCode.Should().BeTrue();\r\n    var rawContent = await httpResponseMessage.Content.ReadAsStringAsync();\r\n    var result = JsonConvert.DeserializeObject<Request>(rawContent);\r\n\r\n    result.Should().BeEquivalentTo(expectedResponse);\r\n}\r\n\r\n[ApiController]\r\npublic class MvcOptionsTestController : ControllerBase\r\n{\r\n    [HttpGet(\"test\")]\r\n    public object Get([FromQuery] Request request)\r\n    {\r\n        return request;\r\n    }\r\n}\r\n\r\npublic class Request\r\n{\r\n    public string Id { get; set; }\r\n\r\n    public string OtherId { get; set; }\r\n}\r\n<\/pre>\n<h3>3. Handling private controllers<\/h3>\n<p>The solution presented below works nice, however it has couple of drawbacks. First of all, it only discovers public non-nested controllers. Second of all, it will always add all controllers from given assembly &#8211; which potentially might affect other integration tests. In order to get rid of these drawbacks, we need to tell <i>ApplicationPartManager<\/i> to include only selected controllers. We can achieve that by adding additional <i>IApplicationFeatureProvider&lt;ControllerFeature&gt;<\/i> to the list of <i>FeatureProviders<\/i>. <\/p>\n<pre lang=csharp>\r\npublic class ExternalControllersFeatureProvider : IApplicationFeatureProvider<ControllerFeature>\r\n{\r\n    private readonly Type[] _controllers;\r\n\r\n    public ExternalControllersFeatureProvider(params Type[] controllers)\r\n    {\r\n        _controllers = controllers ?? Array.Empty<Type>();\r\n    }\r\n\r\n    public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)\r\n    {\r\n        foreach (var controller in _controllers)\r\n        {\r\n            feature.Controllers.Add(controller.GetTypeInfo());\r\n        }\r\n    }\r\n}\r\n\r\n[Fact]\r\npublic async Task DoesNotReturnBadRequest_WhenNonNullableTypeHasNoValue()\r\n{\r\n    var webApplicationFactory = new WebApplicationFactory<Startup>()\r\n        .WithWebHostBuilder(\r\n            builder => builder.ConfigureServices(services =>\r\n            {\r\n                var partManager = (ApplicationPartManager) services\r\n                    .Last(descriptor => descriptor.ServiceType == typeof(ApplicationPartManager))\r\n                    .ImplementationInstance;\r\n\r\n                partManager.FeatureProviders.Add(new ExternalControllersFeatureProvider(typeof(MvcOptionsTestController)));\r\n            }));\r\n\/\/ rest of the code omitted for brevity\r\n}\r\n<\/pre>\n<p>Putting it all together and applying some refactoring we will end up with following code<\/p>\n<pre lang=csharp>\r\ninternal static class WebHostBuilderExtensions\r\n{\r\n    public static IWebHostBuilder WithAdditionalControllers(this IWebHostBuilder builder, params Type[] controllers)\r\n    {\r\n        return builder.ConfigureTestServices(\r\n            services =>\r\n            {\r\n                var partManager = GetApplicationPartManager(services);\r\n\r\n                partManager.FeatureProviders.Add(new ExternalControllersFeatureProvider(controllers));\r\n            });\r\n    }\r\n\r\n    private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)\r\n    {\r\n        var partManager = (ApplicationPartManager)services\r\n            .Last(descriptor => descriptor.ServiceType == typeof(ApplicationPartManager))\r\n            .ImplementationInstance;\r\n        return partManager;\r\n    }\r\n\r\n    private class ExternalControllersFeatureProvider : IApplicationFeatureProvider<ControllerFeature>\r\n    {\r\n        private readonly Type[] _controllers;\r\n\r\n        public ExternalControllersFeatureProvider(params Type[] controllers)\r\n        {\r\n            _controllers = controllers ?? Array.Empty<Type>();\r\n        }\r\n\r\n        public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)\r\n        {\r\n            foreach (var controller in _controllers)\r\n            {\r\n                feature.Controllers.Add(controller.GetTypeInfo());\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\npublic class MvcOptionsConfigurationTests\r\n{\r\n    [Fact]\r\n    public async Task DoesNotReturnBadRequest_WhenNonNullableTypeHasNoValue()\r\n    {\r\n        var webApplicationFactory = new WebApplicationFactory<Startup>()\r\n            .WithWebHostBuilder(\r\n                builder => builder.WithAdditionalControllers(typeof(MvcOptionsTestController)));\r\n\r\n        var httpClient = webApplicationFactory.CreateClient();\r\n        var expectedResponse = new Request\r\n        {\r\n            Id = \"1\"\r\n        };\r\n\r\n        var httpResponseMessage = await httpClient.GetAsync(\"test?id=1\");\r\n\r\n        httpResponseMessage.IsSuccessStatusCode.Should().BeTrue();\r\n        var rawContent = await httpResponseMessage.Content.ReadAsStringAsync();\r\n        var result = JsonConvert.DeserializeObject<Request>(rawContent);\r\n\r\n        result.Should().BeEquivalentTo(expectedResponse);\r\n    }\r\n\r\n    [ApiController]\r\n    private class MvcOptionsTestController: ControllerBase\r\n    {\r\n        [HttpGet(\"test\")]\r\n        public object Get([FromQuery] Request request)\r\n        {\r\n            return request;\r\n        }\r\n    }\r\n\r\n    private class Request\r\n    {\r\n        public string Id { get; set; }\r\n\r\n        public string OtherId { get; set; }\r\n    }\r\n}\r\n<\/pre>\n<p>Source code for this post can be found <a href=\"https:\/\/github.com\/tpodolak\/Blog\/tree\/master\/AspNetCoreAddingControllersInIntegrationTests\">here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Introduction From time to time it might happen, that you need to test certain parts of your ASP.NET Core configuration without hitting publicly visible business-related controllers. For instance, in my case, I wanted to make sure that my ASP.NET Core API behavior is consistent with the original API written in Nancy. As you might [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[317],"tags":[318,293],"class_list":["post-1523","post","type-post","status-publish","format-standard","hentry","category-asp-net-core","tag-asp-net-core","tag-testing"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1523","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=1523"}],"version-history":[{"count":22,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1523\/revisions"}],"predecessor-version":[{"id":1546,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1523\/revisions\/1546"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=1523"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=1523"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=1523"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}