I have web api controller and i want to perform an integration tests. So I followed the article here to configured the in-memory Web API host.
My integration test and web api are two different projects in same VS solution.
Below is the code
Web API Controller
public class DocumentController : ApiController
{
public DocumentController(IDomainService domainService)
{
_domainService = domainService;
}
[HttpPost]
public async Task<IEnumerable<string>> Update([FromBody]IEnumerable<Document> request)
{
return await _domainService.Update(request).ConfigureAwait(false);
}
}
Integration Test
[TestClass]
public class IntegrationTests
{
private HttpServer _server;
private string _url = "http://www.strathweb.com/";
[TestInitialize]
public void Init()
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(name: "Default", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.MessageHandlers.Add()
_server = new HttpServer(config);
}
[TestMethod]
public void UpdateTransformedDocuments()
{
var doc = new Document()
{
// set all properties
}
var client = new HttpClient(_server);
var request = createRequest<Document>("api/document/Update", "application/json", HttpMethod.Post, doc, new JsonMediaTypeFormatter());
using (var response = client.SendAsync(request).Result)
{
// do something with response here
}
}
private HttpRequestMessage createRequest<T>(string url, string mthv, HttpMethod method, T content, MediaTypeFormatter formatter) where T : class
{
Create HttpRequestMessage here
}
}
However im getting error
StatusCode: 404, ReasonPhrase: 'Not Found'
How & where do I tell the HttpServer to execute DocumentController?
Update1
So I fixed above error by changing the [TestIntialize] code as below
[TestInitialize]
public void Init()
{
var config = new HttpConfiguration();
UnityWebApiActivator.Start();
WebApiConfig.Register(config);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
_server = new HttpServer(config);
}
I don't get 404 error now. However Unity is not able to resolve DocumentController. The HttpResponse contains error
An error occurred when trying to create a controller of type
'DocumentController'. Make sure that the controller has a
parameterless public constructor.
In TestInitialize method I'm calling UnityWebApiActivator.Start() which registers all the require types with Unity.
i resolved my 2nd issue by setting 'HttpConfiguration.DependencyResolver'
[TestInitialize]
public void Init()
{
var config = new HttpConfiguration();
//UnityWebApiActivator.Start();
config.DependencyResolver = new UnityHierarchicalDependencyResolver(UnityConfig.GetConfiguredContainer());
WebApiConfig.Register(config);
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
_server = new HttpServer(config);
}
Related
Having an issue getting a service instance in my controller. Followed the documentation from autofac's website but still getting an error. "None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'MyAssembly.Controllers.HeartBeatController' can be invoked with the available services and parameters:\r\nCannot resolve parameter 'MyAssembly.IO.IConfig config' of constructor 'Void .ctor(MyAssembly.IO.IConfig)'."
Here is my Global.asax file
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var builder = new ContainerBuilder();
var config = GlobalConfiguration.Configuration;
WebApiConfig.Register(config);
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
BuildServices(builder);
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
config.EnsureInitialized();
log4net.Config.XmlConfigurator.Configure(new FileInfo(Server.MapPath("~/Web.config")));
}
private void BuildServices(ContainerBuilder builder)
{
builder.RegisterType<Config>().As<IConfig>().InstancePerRequest();
}
}
And here is my interface definition and class. Which is defined in the same assembly.
public interface IConfig
{
string GetSetting(string key);
T GetSetting<T>(string key);
}
public class Config : IConfig
{
public string GetSetting(string key)
{
return ConfigurationManager.AppSettings[key];
}
public T GetSetting<T>(string key)
{
var setting = GetSetting(key);
return setting != null ? (T)System.Convert.ChangeType(setting, typeof(T)) : default(T);
}
}
This is so ridiculous but I failed to build the services before the container. Oversight on my part and the below works.
BuildServices(builder);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
config.EnsureInitialized();
i have SwaggerConfig Setup like this
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "My.API");
})
.EnableSwaggerUi(c =>
{
});
}
}
and WebApiConfig setup like this
public static class WebApiConfig
{
/// <summary>
/// Register Method for Configuration Settings.
/// </summary>
/// <returns>Returns a HttpConfiguration which is a complex object which represents a configuration of HttpServer.
/// </returns>
public static HttpConfiguration Register()
{
var config = new HttpConfiguration();
// Web API routes
config.MapHttpAttributeRoutes();
config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());
// SetupOData(config);
// require authentication for all controllers
config.Filters.Add(new AuthorizeAttribute());
config.Filters.Add(new HostAuthenticationAttribute("Bearer"));
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "GetPagedData",
routeTemplate: "{controller}/{pageNumber}/{pageSize}"
);
// Web API configuration and services
config.EnableCors();
// pulling out the XML for now -teg
config.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("application/json-patch+json"));
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("text/html"));
var logger = new LoggerConfiguration()
.ReadFrom.AppSettings()
.CreateLogger();
config.Services.Add(typeof(IExceptionLogger), new MedefisExceptionLogger(logger));
config.Filters.Add(new CommonExceptionFilterAttribute());
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Services.Replace(typeof(IDocumentationProvider),
new XmlCommentDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Medefis.API.XML")));
return config;
}
}
in the Startup.cs i am calling SwaggerConfig.Register() and few lines later i am also calling WebApiConfig.Register()
public class Startup
{
/// <summary>
/// Configuration for IAppBuilder on MedefisAPIUri
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
SwaggerConfig.Register();
var logger = new LoggerConfiguration()
.ReadFrom.AppSettings()
.CreateLogger();
logger.Information("Api Startup");
AutoMapperConfiguration.Configure();
//CreateFoldersOnFileSystem();
app.UseCors(CorsOptions.AllowAll);
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
AntiForgeryConfig.UniqueClaimTypeIdentifier = IdentityServer3.Core.Constants.ClaimTypes.Subject;
if (ConfigurationManager.AppSettings["UseIdentityServer"] == "True")
{
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = MedefisConstants.MedefisSTSConfig,
RequiredScopes = new[] { MedefisConstants.Scopes.api },
ClientId = MedefisConstants.Scopes.api,
ClientSecret = MedefisConstants.Secrets.InternalAPIClientSecret
}
);
}
// https://stackoverflow.com/questions/15637491/how-to-inject-httpcontextbase-using-autofac-in-asp-net-mvc-4
builder.Register(c => new HttpContextWrapper(HttpContext.Current)).As<HttpContextBase>().InstancePerRequest();
builder.RegisterType<ContactEmailRepository>().As<IContactEmailRepository>().InstancePerRequest();
builder.RegisterType<EmailMedefisUsersHelper>().As<IEmailMedefisUsersHelper>().InstancePerRequest();
builder.RegisterType<AgencyBiddingProfileService>().As<IAgencyBiddingProfileService>().InstancePerRequest();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<UploadDocumentHelper>().As<IUploadDocumentHelper>().InstancePerRequest();
builder.RegisterModule(new ValidationModule());
//Service
//builder.RegisterType<..... // su registerations goes here
//Setup
var config = WebApiConfig.Register();
builder.RegisterWebApiFilterProvider(config);
builder.RegisterInstance(config).As<HttpConfiguration>().SingleInstance().PropertiesAutowired();
var container = builder.Build();
config.EnableDependencyInjection();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
app.Use(typeof(WebApiAuthUserMiddleware));
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseWebApi(config);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
config.EnsureInitialized();
}
}
the problem is that when i call http://localhost:xxxx/swagger it redirects to /swagger/ui/index but i get the error
No type was found that matches the controller named 'swagger'
please not the i am using Swashbuckle.Net45 version 5.2.1
Update
after prefixing the paths with "api/" in routeTemplate: "api/{controller}/{id}""
i was able to load the UI but none of paths and definitions are empty.
Solution was not to use SwaggerConfig.Register() separately instead do this configuration inside WebApiConfig.
I am trying to use Ninject to inject dependency into a Self-host Web API Controller. I am success to inject the dependency as follows:
public static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<ITestAlert>().To<TestAlert>();
return kernel;
}
catch (Exception)
{
throw;
}
}
and
var server = WebApp.Start(baseAddress, (appBuilder) =>
{
// Configure Web API for self-host.
var config = new HttpConfiguration();
config.DependencyResolver = new NinjectResolver(NinjectConfig.CreateKernel());
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
});
However, what I want to do is to pass an initialized instance from outside into the Controller's constructor. For instance, I have initialized an instance
public interface ITestAlert
{
string Fullname { get; set; }
}
and
public class TestAlert : ITestAlert
{
private string _fullname;
public string Fullname
{
get
{
return _fullname;
}
set
{
_fullname = value;
}
}
}
The instance is initialized as
public ITestAlert _testAlert;
_testAlert = new TestAlert
{
FullName = "Dummy"
};
What I expect is to get "Dummy" output string from the Get action method of the Controller,
private ITestAlert _testAlert = null;
public ValuesController(ITestAlert testAlert)
{
_testAlert = testAlert;
}
public string Get()
{
return _testAlert.Fullname;
}
However, I can only get a "null" return! Would you please advise how to pass the initialized instance into the Controller's constructor. Thanks.
Adding a dedicated binding should do the trick :
kernel.Bind<ITestAlert>()
.ToConstant(new TestAlert{FullName = Dummy"}).WhenInjectedInto<ValuesController>();
My global.asax looks like below
private void BuildIocContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new BootstrapModule());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); //Set the WebApi DependencyResolver
}
protected void Application_Start()
{
BuildIocContainer();
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
I have built a bootstrap module for autofac like the one below
public class BootstrapModule : Autofac.Module
{
private Assembly AssemblyOf<T>()
{
return typeof(T).Assembly;
}
private Assembly WebAssembly
{
get { return Assembly.GetExecutingAssembly(); }
}
private void RegisterMvc(ContainerBuilder builder)
{
builder.RegisterType<AsyncControllerActionInvoker>()
.As<IActionInvoker>();
builder.RegisterControllers(WebAssembly)
.InjectActionInvoker();
builder.RegisterApiControllers(WebAssembly).InjectActionInvoker();
}
private void RegisterLogger(ContainerBuilder builder)
{
builder.Register(CreateLogger)
.SingleInstance();
builder.Register(_ => new NLogWrapper(LogManager.GetLogger("DefaultLogger")))
.As<Logging.ILogger>()
.SingleInstance();
}
private static System.Func<Type, Logging.ILogger> CreateLogger(IComponentContext componentContext)
{
return type => new NLogWrapper(LogManager.GetLogger(type.FullName));
}
protected override void Load(ContainerBuilder builder)
{
RegisterLogger(builder);
RegisterMvc(builder);
builder.RegisterAssemblyTypes(WebAssembly)
.AsImplementedInterfaces();
builder.RegisterType<UserService>()
.As<IUserService>()
.InstancePerRequest();
builder.RegisterAssemblyTypes(typeof(IUserService).Assembly)
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof(IUserRepository).Assembly)
.AsImplementedInterfaces();
builder.RegisterFilterProvider();
}
}
Now, when I try to hit account controller through postman client,
private IUserService _userService;
public AccountController(IUserService userService)
{
_userService = userService;
}
// POST api/Account/Register
[AllowAnonymous]
[Route("Register")]
public HttpStatusCode Register(User model)
{
if (!ModelState.IsValid)
{
return HttpStatusCode.BadRequest;
}
// TODO : Write mapper if needed
var user = new ToolUser()
{
FirstName = model.FirstName,
LastName = model.LastName,
EmailID = model.EmailID,
DOB = Convert.ToDateTime(model.DateOfBirth),
Gender = model.Gender.ToString(),
TenandID = model.TenantID,
Password = model.Password
};
HttpStatusCode result = _userService.RegisterUser(user);
return result;
}
I get the error
"An error occurred when trying to create a controller of type 'AccountController'. Make sure that the controller has a parameterless public constructor."
What am I doing wrong?
You should have something like
HttpConfiguration config = new HttpConfiguration();
somewhere, which you use to register your routes etc.
Pass that config to your BuildIocContainer() mehod and add the line
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
instead of
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); //Set the WebApi DependencyResolver
Hope that helps
I want to do integration tests on my Web API without depending on my business layer interfaces.
When this action is run:
1) I want to mock the _service object and just verify that is is called
2) I want to assert that the correct StatusCode is returned
Number 2 is no problem but how can I mock the _service object (ISchoolyearService) when I do not control/start the creation of the api controller manually because this is a task done in unit testing the controller. But I do not want to unit test my API !
[RoutePrefix("api/schoolyears")]
public class SchoolyearController : ApiController
{
private readonly ISchoolyearService _service;
public SchoolyearController(ISchoolyearService service)
{
_service = service;
}
[Route("")]
[HttpPost]
public HttpResponseMessage Post([FromBody]SchoolyearCreateRequest request)
{
_service.CreateSchoolyear(request);
return Request.CreateResponse(HttpStatusCode.Created);
}
Following is a crude example of how you can do with in-memory integration testing. Here I am using Unity.WebApi.UnityDependencyResolver to inject mock dependencies. You can use any other IoC container similarly.
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using Unity.WebApi;
namespace WebApplication251.Tests.Controllers
{
[TestClass]
public class PeopleControllerTest
{
string baseAddress = "http://dummyhost/";
[TestMethod]
public void PostTest()
{
HttpConfiguration config = new HttpConfiguration();
// use the configuration that the web application has defined
WebApiConfig.Register(config);
//override the dependencies with mock ones
RegisterMockDependencies(config);
HttpServer server = new HttpServer(config);
//create a client with a handler which makes sure to exercise the formatters
HttpClient client = new HttpClient(new InMemoryHttpContentSerializationHandler(server));
SchoolyearCreateRequest req = new SchoolyearCreateRequest();
using (HttpResponseMessage response = client.PostAsJsonAsync<SchoolyearCreateRequest>(baseAddress + "api/schoolyears", req).Result)
{
Assert.IsNotNull(response.Content);
Assert.IsNotNull(response.Content.Headers.ContentType);
Assert.AreEqual<string>("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString());
SchoolyearCreateRequest recSCR = response.Content.ReadAsAsync<SchoolyearCreateRequest>().Result;
//todo: verify data
}
}
private void RegisterMockDependencies(HttpConfiguration config)
{
var unity = new UnityContainer();
unity.RegisterType<ISchoolyearService, MockSchoolyearService>();
config.DependencyResolver = new UnityDependencyResolver(unity);
}
}
[RoutePrefix("api/schoolyears")]
public class SchoolyearController : ApiController
{
private readonly ISchoolyearService _service;
public SchoolyearController(ISchoolyearService service)
{
_service = service;
}
[Route]
[HttpPost]
public HttpResponseMessage Post([FromBody]SchoolyearCreateRequest request)
{
_service.CreateSchoolyear(request);
return Request.CreateResponse(HttpStatusCode.Created);
}
}
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content = await ConvertToStreamContentAsync(request.Content);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Content = await ConvertToStreamContentAsync(response.Content);
return response;
}
private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
{
if (originalContent == null)
{
return null;
}
StreamContent streamContent = originalContent as StreamContent;
if (streamContent != null)
{
return streamContent;
}
MemoryStream ms = new MemoryStream();
await originalContent.CopyToAsync(ms);
// Reset the stream position back to 0 as in the previous CopyToAsync() call,
// a formatter for example, could have made the position to be at the end
ms.Position = 0;
streamContent = new StreamContent(ms);
// copy headers from the original content
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return streamContent;
}
}
}