I have a controller class with 2 service interfaces: one is a post method of external URL and the second is a local repository. In the controller test class, I just want to moq the interface1, not the second. How can I do this? Or what is the best way to test this controller?
[Route("api/MyAPI")]
[ApiController]
public class MyAPIController : Controller
{
private readonly Interface1 _interface1;
private readonly Interface2 _interface2;
public ProfitLossGrowthRateController(Interface1 interface1,
Interface2 interface2)
{
_interface1= interface1;
_interface2 = interface2;
}
[HttpPost("CodeAPI")]
[Produces(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(List<Response>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Error), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Error), StatusCodes.Status500InternalServerError)]
public async Task<List<Response>> GetCodeAPIController
(Request request)
{
var response1 = await _interface1.GetCodesByFilter(request);
return await _interface2.GetGrowthRate(response1.Codes);
}
}
, meanwhile, in the first version of this controller when I don't have the first interface, I write my controller test like this and it works correctly:
public class MyAPIControllerTest : IntegrationBaseTest
{
public MyAPIControllerTest ()
: base(nameof(Repository))
{
DataProvider.InsertData<Data1>
(Context, nameof(Data1));
}
[Theory]
[InlineData("/api/MyAPI/CodeAPI")]
public async Task GetGrowthRate(string url)
{
var request = new Request()
{
Filter = new List<string>() { "111" }
};
var content = await HttpHandler.PostURI<Request>(url, request, Factory);
List<Response> response = JsonConvert.DeserializeObject<List<Response>>(content);
// Assert
Assert.NotNull(response);
Assert.Single(response);
Assert.Equal("111", response[0].CompanyNationalCode);
}
}
Related
I am the beginner of writing unit tests for asp.net. I created a simple project and try to start my testing journey. However, I met two errors with the same issue:"object reference not set to instance of an object" The first place is in the home controller as below:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
Here is my test method:
public class HomeControllerUnitTests
{
ILogger<HomeController> _logger;
[Fact]
public void Error_ActionExecutes_ReturnsAViewResult()
{
// Arrange
var homeController = new HomeController(_logger);
// Act
var result = homeController.Error() as ViewResult;
// Assert
Assert.Null(result.ViewData.Model);
}
}
The second place is in the Movie Controller:
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
}
My test method is as below:
public class MoviesControllerUnitTests
{
private Mock<MvcMovieContext> _mock;
[Fact]
public async Task Index_ActionExecutes_ReturnsAViewResult()
{
// Arrange
MoviesController controller = new MoviesController(_mock.Object);
// Act
var result = await controller.Index() as Task<ViewResult>;
// Assert
Assert.IsType<ViewResult>(result);
}
}
Please help me and thanks in advance.
Below the Object reference not set to an instance of an object line there should be an indication about which file and line the error occurred, which helps you to determine which variables are null (but you could also use the debugger).
For the MoviesControllerUnitTests this probably is the _mock variable, so be sure to initialize it as shown in the docs, e.g.:
private Mock<MvcMovieContext> _mock = new Mock<MvcMovieContext>();
For the HomeControllerUnitTests you might need to mock the Activity or set a HttpContext (see e.g. this question).
i have an problem with call to api, i get ambiguousMatchException: The request matched multiple endpoints
or page not found.
this happen after i add testApi to my controller, before all works good.
why it's happen and how to fix it? i want to add more function to this controller.
thank's!!!!!!
my code:
namespace AutomationTool.Api
{
[Route("api/[controller]")]
[ApiController]
public class TestCasesController : ControllerBase
{
private readonly ApplicationDbContext _context;
public TestCasesController(ApplicationDbContext context)
{
_context = context;
}
[HttpGet("{id:int}")] -- > here the problem
public int testApi(int id)
{
return 1;
}
// GET: api/TestCases
[HttpGet]
public async Task<ActionResult<IEnumerable<TestCase>>> GetTestCases()
{
return await _context.TestCases.ToListAsync();
}
// GET: api/TestCases/5
[HttpGet("{id}")]
public async Task<ActionResult<TestCase>> GetTestCase(int id)
{
var testCase = await _context.TestCases.FindAsync(id);
if (testCase == null)
{
return NotFound();
}
return testCase;
}
}
}
I need to handle an incoming request which is of the form:
//ohif/study/1.1/series
Note the exta slash at the front
My controller signature is:
[Route("ohif/study/{studyUid}/series")]
[HttpGet]
public IActionResult GetStudy(string studyUid)
If I modify the incoming request to /ohif/study/1.1/series it works fine
however when I use //ohif/study/1.1/series, the route is not hit
Additionally I also tried: [Route("/ohif/study/{studyUid}/series")]
and [Route("//ohif/study/{studyUid}/series")]
Both fail. I unfortunately cannot change the incoming request as it is from an external application. Is there some trick to handle this route? I am working in .NET Core 3.0.
Update NOTE:
I have logging activated and I see that asp.net core is analyzing the route, I have the message:
No candidates found for the request path '//ohif/study/1.1/series'
for the logger Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware
What about the middleware to handle double slash?
app.Use((context, next) =>
{
if (context.Request.Path.Value.StartsWith("//"))
{
context.Request.Path = new PathString(context.Request.Path.Value.Replace("//", "/"));
}
return next();
});
Rewrite the URL at the web server-level, e.g. for IIS, you can use the URL Rewrite Module to automatically redirect //ohif/study/1.1/series to /ohif/study/1.1/series. This isn't a job for your application.
I took Ravi's answer and fleshed out a middleware. The middleware is nice because it is encapsulated, easily testable, can inject a logger, more readable, etc.
app.UseDoubleSlashHandler();
The code and tests:
public class DoubleSlashMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<DoubleSlashMiddleware> _logger;
public DoubleSlashMiddleware(RequestDelegate next, ILogger<DoubleSlashMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation($"Invoking {nameof(DoubleSlashMiddleware)} on {context.Request.Path}");
context.Request.Path = context.Request.Path.FixDoubleSlashes();
// Call the next delegate/middleware in the pipeline.
await _next(context);
}
}
public static class DoubleSlashMiddlewareExtensions
{
public static IApplicationBuilder UseDoubleSlashHandler(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<DoubleSlashMiddleware>();
}
}
[TestClass()]
public class DoubleSlashMiddlewareTests
{
private DoubleSlashMiddleware _sut;
private ILogger<DoubleSlashMiddleware> _logger;
private bool _calledNextMiddlewareInPipeline;
[TestInitialize()]
public void TestInitialize()
{
_logger = Substitute.For<ILogger<DoubleSlashMiddleware>>();
Task Next(HttpContext _)
{
_calledNextMiddlewareInPipeline = true;
return Task.CompletedTask;
}
_sut = new DoubleSlashMiddleware(Next, _logger);
}
[TestMethod()]
public async Task InvokeAsync()
{
// Arrange
_calledNextMiddlewareInPipeline = false;
// Act
await _sut.InvokeAsync(new DefaultHttpContext());
// Assert
_logger.ReceivedWithAnyArgs(1).LogInformation(null);
Assert.IsTrue(_calledNextMiddlewareInPipeline);
}
}
String method to do the replacement:
public static class RoutingHelper
{
public static PathString FixDoubleSlashes(this PathString path)
{
if (string.IsNullOrWhiteSpace(path.Value))
{
return path;
}
if (path.Value.Contains("//"))
{
return new PathString(path.Value.Replace("//", "/"));
}
return path;
}
}
[TestClass()]
public class RoutingHelperTests
{
[TestMethod()]
[DataRow(null, null)]
[DataRow("", "")]
[DataRow("/connect/token", "/connect/token")]
[DataRow("//connect/token", "/connect/token")]
[DataRow("/connect//token", "/connect/token")]
[DataRow("//connect//token", "/connect/token")]
[DataRow("/connect///token", "/connect/token")]
public void FixDoubleSlashes(string input, string expected)
{
// Arrange
var path = new PathString(input);
// Act
var actual = path.FixDoubleSlashes();
// Assert
Assert.AreEqual(expected, actual.Value);
}
}
I'm new to NHibernate and trying to use it in ASP.NET WEB API. Firstly I used it successfully with one table named "Category" which the controller class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class CategoryController : ApiController
{
private readonly ISession _session;
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ISession session,
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_session = session;
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
return _session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
public Category Get(long id)
{
var category = _categoryFetcher.GetCategory(id);
return _categoryMapper.CreateCategory(category);
}
public HttpResponseMessage Post(HttpRequestMessage request, Category category)
{
var modelCategory = new Data.Model.Category
{
Description = category.Description,
CategoryName = category.CategoryName
};
_session.Save(modelCategory);
var newCategory = _categoryMapper.CreateCategory(modelCategory);
//var href = newCategory.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newCategory);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Category>().List();
foreach (var category in categories)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var category = _session.Get<Data.Model.Category>(id);
if (category != null)
{
_session.Delete(category);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Category Put(long id, Category category)
{
var modelCateogry = _categoryFetcher.GetCategory(id);
modelCateogry.CategoryName = category.CategoryName;
modelCateogry.Description = category.Description;
_session.SaveOrUpdate(modelCateogry);
return _categoryMapper.CreateCategory(modelCateogry);
}
}
}
But when I add The "Product" table which has a foreign key of the Category table, the product controller doesn't work and throws below exception:
No session bound to the current context
ProductController class is as follow:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;
namespace TestMVCProject.Web.Api.Controllers
{
[LoggingNHibernateSession]
public class ProductController : ApiController
{
private readonly ISession _session;
private readonly IProductMapper _productMapper;
private readonly IHttpProductFetcher _productFetcher;
public ProductController(
ISession session,
IProductMapper productMapper,
IHttpProductFetcher productFetcher)
{
_session = session;
_productMapper = productMapper;
_productFetcher = productFetcher;
}
public IEnumerable<Product> Get()
{
return _session
.QueryOver<Data.Model.Product>()
.List()
.Select(_productMapper.CreateProduct)
.ToList();
}
public Product Get(long id)
{
var product = _productFetcher.GetProduct(id);
return _productMapper.CreateProduct(product);
}
public HttpResponseMessage Post(HttpRequestMessage request, Product product)
{
var modelProduct = new Data.Model.Product
{
Description = product.Description,
ProductName = product.ProductName
};
_session.Save(modelProduct);
var newProduct = _productMapper.CreateProduct(modelProduct);
//var href = newproduct.Links.First(x => x.Rel == "self").Href;
var response = request.CreateResponse(HttpStatusCode.Created, newProduct);
//response.Headers.Add("Location", href);
return response;
}
public HttpResponseMessage Delete()
{
var categories = _session.QueryOver<Data.Model.Product>().List();
foreach (var product in categories)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public HttpResponseMessage Delete(long id)
{
var product = _session.Get<Data.Model.Product>(id);
if (product != null)
{
_session.Delete(product);
}
return new HttpResponseMessage(HttpStatusCode.OK);
}
public Product Put(long id, Product product)
{
var modelProduct = _productFetcher.GetProduct(id);
modelProduct.ProductName = product.ProductName;
modelProduct.Description = product.Description;
_session.SaveOrUpdate(modelProduct);
return _productMapper.CreateProduct(modelProduct);
}
}
}
and the mapping class for Product table:
using TestMVCProject.Data.Model;
using FluentNHibernate.Mapping;
namespace TestMVCProject.Data.SqlServer.Mapping
{
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.ProductId);
Map(x => x.ProductName).Not.Nullable();
Map(x => x.Description).Nullable();
Map(x => x.CreateDate).Not.Nullable();
Map(x => x.Price).Not.Nullable();
References<Category>(x => x.CategoryId).Not.Nullable();
}
}
}
What is wrong?
Your snippets are missing the way, how the ISessionFactory is created and how ISession is passed into your controllers... You should follow this really comprehensive story (by Piotr Walat):
NHibernate session management in ASP.NET Web API
Where you can see that we, can use 2.3. Contextual Sessions:
NHibernate.Context.WebSessionContext - stores the current session in HttpContext. You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.
The configuration
<session-factory>
..
<property name="current_session_context_class">web</property>
</session-factory>
In the article you can check that we need at the app start initialize factory (just an extract):
public class WebApiApplication : System.Web.HttpApplication
{
private void InitializeSessionFactory() { ... }
protected void Application_Start()
{
InitializeSessionFactory();
...
Next we should create some AOP filter (just an extract):
public class NhSessionManagementAttribute : ActionFilterAttribute
{
...
public override void OnActionExecuting(HttpActionContext actionContext)
{
// init session
var session = SessionFactory.OpenSession();
...
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// close session
...
session = CurrentSessionContext.Unbind(SessionFactory);
}
For more details check the source mentioned above
Your approach of passing the session to the constructor of the controller factory does not seems to be working, there are a few ways to do this
1. Using dependency injection
If you are using a dependency injection framework, you have to configure controller so that it's constructed per request, it should looks like this (I have used the code for Ninject)
Step 1 - setup the session for injection
public class DIModule : NinjectModule
{
public override void Load()
{
this.Bind<ISessionFactory>()... bind to the session factory
this.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession())
.InRequestScope();
}
private ISession CreateSessionProxy(IContext ctx)
{
var session = (ISession)this.proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(ISession), new[] { typeof(ISessionImplementor) }, ctx.Kernel.Get<SessionInterceptor>());
return session;
}
}
Step 2 - Create the controller factory so that it will inject the session when resolving
public class NinjectControllerFactory : DefaultControllerFactory, IDependencyResolver
{
private IDependencyResolver _defaultResolver;
public NinjectControllerFactory(IDependencyResolver defaultResolver)
{
_defaultResolver = defaultResolver;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return controllerType == null
? null
: (IController)DependencyKernel.Kernel.Get(controllerType);
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
try
{
return DependencyKernel.Kernel.Get(serviceType);
}
catch (Exception)
{
return GetService(serviceType);
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
object item = DependencyKernel.Kernel.Get(serviceType);
return new List<object>() {item};
}
catch (Exception)
{
return GetServices(serviceType);
}
}
public void Dispose()
{
}
}
Step 3 - Register the controller factory
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var factory = new NinjectControllerFactory(GlobalConfiguration.Configuration.DependencyResolver);
ControllerBuilder.Current.SetControllerFactory(factory);
GlobalConfiguration.Configuration.DependencyResolver = factory;
}
}
Now what will happen is that when your controller is created it will inject the a new NH session per each request.
2. Using a filter
This is much simpler, but you may need to change your controllers a bit this to work,
Step 1 - Setup the correct session context for the factory
_sessionFactory = CreateConfiguration()
.ExposeConfiguration(c => c.SetProperty("current_session_context_class","web"))
.BuildSessionFactory();
Step 2 - Create the filter
public class SessionPerRequestAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
NHibernate.Context.CurrentSessionContext.Bind(session);
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
session.Flush();
session.Clear();
session.Close();
base.OnActionExecuted(actionExecutedContext);
}
}
Step 3 - Register the filter in global configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//Do other config here
config.Filters.Add(new SessionPerRequestAttribute());
}
}
Step 4 - Modify your controller a bit,
public class CategoryController : ApiController
{
private readonly ICategoryMapper _categoryMapper;
private readonly IHttpCategoryFetcher _categoryFetcher;
public CategoryController(
ICategoryMapper categoryMapper,
IHttpCategoryFetcher categoryFetcher)
{
_categoryMapper = categoryMapper;
_categoryFetcher = categoryFetcher;
}
public IEnumerable<Category> Get()
{
var session = SessionFactory.GetCurrentSession();
return session
.QueryOver<Data.Model.Category>()
.List()
.Select(_categoryMapper.CreateCategory)
.ToList();
}
}
Here what happens is, when a request comes it will create a new session and it is bound to the request context and same is used for the web API method.
I've got a thread that sends emails around. I need to generate ActionLinks as part of the content of the email so the user can click on the link and be redirected to the website, exactly to the required page. I tried to instantiate a UrlHelper class and use it's Action method to generate the link but since threads don't run within the context of any request I get exceptions at the time of generating the ActionLink.
How can I do this?
You need to fake HttpContextBase and pass this to an UrlHelper which you can use in a thread without an HttpContext. Here is the rough idea, although you will need to create a class around it etc, this is a quick proof of concept as unit tests don't have an HttpContext either.
[TestFixture]
public class RouteTestClass
{
private UrlHelper helper;
public RouteTestClass()
{
MvcApplication.RegisterRoutes(RouteTable.Routes); //You dont need to do this if its done in global.asax!
var c = new RequestContext(new FakeContext(), new RouteData());
helper = new UrlHelper(c, RouteTable.Routes);
}
[Test]
public void TestGetHomeIndex()
{
var url = helper.Action("Index", "Home");
Assert.AreEqual("/",url);
}
}
public class FakeContext : HttpContextBase
{
public override HttpRequestBase Request { get { return new FakeRequest(); } }
public override HttpResponseBase Response { get { return new FakeResponse(); } }
}
public class FakeRequest : HttpRequestBase
{
public override string ApplicationPath { get { return "/"; } }
public override NameValueCollection ServerVariables { get { return new NameValueCollection(); } }
}
public class FakeResponse : HttpResponseBase
{
public override string ApplyAppPathModifier(string virtualPath)
{
return virtualPath;
}
}
Edit
Looking at this answer, I tidied the code up a little as I don't need to create fakes for HttpRequestBase and HttpResponseBase myself.
[TestFixture]
public class RouteTestClass
{
private UrlHelper helper;
public RouteTestClass()
{
MvcApplication.RegisterRoutes(RouteTable.Routes);
var req = new HttpRequest("/", "http://www.yoururl.com", "");
var resp = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(req, resp);
var c = new RequestContext(new HttpContextWrapper(httpContext), new RouteData());
helper = new UrlHelper(c, RouteTable.Routes);
}
[Test]
public void TestGetHomeIndex()
{
var url = helper.Action("Index", "Home");
Assert.AreEqual("/",url);
}
}
You can give the thread access to an existing UrlHelper by passing it to the thread starter. If your thread is started from a controller, just pass the UrlHelper in the controller's Url property:
new Thread(
urlHelper =>
{
var url =
((UrlHelper)urlHelper)
.Action("Index", "Home", new { Id = 5 });
// use url here
}
).Start(Url);