I have written an IDbCommandInterceptor to intercept calls to EntityFrameworkCore DbContext. Now I would like to write unit tests for the interceptor. I am able to intercept and run custom logic for an actual db (tested with SqlServer and MySql), however when testing with an InMemoryDatabase (provided by .Net for testing purposes), my interceptor methods are never called.
Here's how I'm setting up my unit test:
TestDbContext class:
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions options) : base(options) { }
public DbSet<User> Users { get; set; }
}
public class User
{
public int UserId { get; set; }
}
This is how I'm initializing the context in my test:
private TestDbContext GetTestDbContext()
{
var options = new DbContextOptionsBuilder<TestDbContext>()
.UseInMemoryDatabase(databaseName: "TestDB")
.AddInterceptors(new MyInterceptor())
.Options;
var context = new TestDbContext(options);
// Seed data
context.Users.Add(new User { UserId = 1 });
context.Users.Add(new User { UserId = 2 });
context.Users.Add(new User { UserId = 3 });
context.SaveChanges();
return context;
}
Now, when I call SaveChanges() or do a query on this context, I expect it to invoke my MyInterceptor's overridden ReaderExecuted() method the same way it does when I use an actual db.
Am I missing something? Or DbCommandInterceptor does not work with InMemoryDatabase?
The InMemory provider is not a relational provider, and therefore does not implement DbCommand interceptors. You can use a SQL Server / SQL Server LocalDb / SQlite database for this kind of testing.
I have an ASP.NET MVC Core application that I am writing unit tests for. One of the action methods uses User name for some functionality:
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
which obviously fails in the unit test. I looked around and all suggestions are from .NET 4.5 to mock HttpContext. I am sure there is a better way to do that. I tried to inject IPrincipal, but it threw an error; and I even tried this (out of desperation, I suppose):
public IActionResult Index(IPrincipal principal = null) {
IPrincipal user = principal ?? User;
SettingsViewModel svm = _context.MySettings(user.Identity.Name);
return View(svm);
}
but this threw an error as well.
Couldn't find anything in the docs either...
The controller’s User is accessed through the HttpContext of the controller. The latter is stored within the ControllerContext.
The easiest way to set the user is by assigning a different HttpContext with a constructed user. We can use DefaultHttpContext for this purpose, that way we don’t have to mock everything. Then we just use that HttpContext within a controller context and pass that to the controller instance:
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, "example name"),
new Claim(ClaimTypes.NameIdentifier, "1"),
new Claim("custom-claim", "example claim value"),
}, "mock"));
var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext() { User = user }
};
When creating your own ClaimsIdentity, make sure to pass an explicit authenticationType to the constructor. This makes sure that IsAuthenticated will work correctly (in case you use that in your code to determine whether a user is authenticated).
In previous versions you could have set User directly on the controller, which made for some very easy unit tests.
If you look at the source code for ControllerBase you will notice that the User is extracted from HttpContext.
/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;
and the controller accesses the HttpContext via ControllerContext
/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;
You will notice that these two are read only properties. The good news is that ControllerContext property allows for setting it's value so that will be your way in.
So the target is to get at that object. In Core HttpContext is abstract so it is a lot easier to mock.
Assuming a controller like
public class MyController : Controller {
IMyContext _context;
public MyController(IMyContext context) {
_context = context;
}
public IActionResult Index() {
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
return View(svm);
}
//...other code removed for brevity
}
Using Moq, a test could look like this
public void Given_User_Index_Should_Return_ViewResult_With_Model() {
//Arrange
var username = "FakeUserName";
var identity = new GenericIdentity(username, "");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
var model = new SettingsViewModel() {
//...other code removed for brevity
};
var mockContext = new Mock<IMyContext>();
mockContext.Setup(m => m.MySettings(username)).Returns(model);
var controller = new MyController(mockContext.Object) {
ControllerContext = new ControllerContext {
HttpContext = mockHttpContext.Object
}
};
//Act
var viewResult = controller.Index() as ViewResult;
//Assert
Assert.IsNotNull(viewResult);
Assert.IsNotNull(viewResult.Model);
Assert.AreEqual(model, viewResult.Model);
}
There is also the possibility to use the existing classes, and mock only when needed.
var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = user.Object
}
};
In my case, I needed to make use of Request.HttpContext.User.Identity.IsAuthenticated, Request.HttpContext.User.Identity.Name and some business logic sitting outside of the controller. I was able to use a combination of Nkosi's, Calin's and Poke's answer for this:
var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);
var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();
var controller = new MyController(...);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
User = mockPrincipal.Object
};
var result = controller.Get() as OkObjectResult;
//Assert results
mockAuthHandler.Verify();
I want to hit my Controllers directly and just use DI like AutoFac. To do this I first registering ContextController.
var identity = new GenericIdentity("Test User");
var httpContext = new DefaultHttpContext()
{
User = new GenericPrincipal(identity, null)
};
var context = new ControllerContext { HttpContext = httpContext};
builder.RegisterInstance(context);
Next I enable property injection when I register the Controllers.
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();
Then User.Identity.Name is populated, and I do not need to do anything special when calling a method on my Controller.
public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
var requestedBy = User.Identity?.Name;
..................
I would look to implement an Abstract Factory Pattern.
Create an interface for a factory specifically for providing user names.
Then provide concrete classes, one which provides User.Identity.Name, and one that provides some other hard coded value that works for your tests.
You can then use the appropriate concrete class depending on production versus test code. Perhaps looking to pass the factory in as a parameter, or switching to the correct factory based on some configuration value.
interface IUserNameFactory
{
string BuildUserName();
}
class ProductionFactory : IUserNameFactory
{
public BuildUserName() { return User.Identity.Name; }
}
class MockFactory : IUserNameFactory
{
public BuildUserName() { return "James"; }
}
IUserNameFactory factory;
if(inProductionMode)
{
factory = new ProductionFactory();
}
else
{
factory = new MockFactory();
}
SettingsViewModel svm = _context.MySettings(factory.BuildUserName());
I got a brownfield .net 4.8 project that I needed to convert to .net 5.0 and I wanted to keep as much of the original code as possible, including the unit-/integration tests. The test for Controllers relied on the Context a lot so I created this Extension method to enable setting tokens, claims and headers:
public static void AddContextMock(
this ControllerBase controller,
IEnumerable<(string key, string value)> claims = null,
IEnumerable<(string key, string value)> tokens = null,
IEnumerable<(string key, string value)> headers = null)
{
HttpContext mockContext = new DefaultHttpContext();
if(claims != null)
{
mockContext.User = SetupClaims(claims);
}
if(tokens != null)
{
mockContext.RequestServices = SetupTokens(tokens);
}
if(headers != null)
{
SetupHeaders(mockContext, headers);
}
controller.ControllerContext = new ControllerContext()
{
HttpContext = mockContext
};
}
private static void SetupHeaders(HttpContext mockContext, IEnumerable<(string key, string value)> headers)
{
foreach(var header in headers)
{
mockContext.Request.Headers.Add(header.key, header.value);
}
}
private static ClaimsPrincipal SetupClaims(IEnumerable<(string key, string value)> claimValues)
{
var claims = claimValues.Select(c => new Claim(c.key, c.value));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "mock"));
}
private static IServiceProvider SetupTokens(IEnumerable<(string key, string value)> tokenValues)
{
var mockServiceProvider = new Mock<IServiceProvider>();
var authenticationServiceMock = new Mock<IAuthenticationService>();
var authResult = AuthenticateResult.Success(
new AuthenticationTicket(new ClaimsPrincipal(), null));
var tokens = tokenValues.Select(t => new AuthenticationToken { Name = t.key, Value = t.value });
authResult.Properties.StoreTokens(tokens);
authenticationServiceMock
.Setup(x => x.AuthenticateAsync(It.IsAny<HttpContext>(), null))
.ReturnsAsync(authResult);
mockServiceProvider.Setup(_ => _.GetService(typeof(IAuthenticationService))).Returns(authenticationServiceMock.Object);
return mockServiceProvider.Object;
}
This uses Moq but can be adapted to other mocking frameworks. The authentication type is hardcoded to "mock" since I rely on default authentication but this could be supplied as well.
It is used as such:
_controllerUnderTest.AddContextMock(
claims: new[]
{
(ClaimTypes.Name, "UserName"),
(ClaimTypes.MobilePhone, "1234"),
},
tokens: new[]
{
("access_token", "accessTokenValue")
},
headers: new[]
{
("header", "headerValue")
});
If you're using Razor pages and want to override the claims:
[SetUp]
public void Setup()
{
var user = new ClaimsPrincipal(new ClaimsIdentity(
new Claim[] {
new("dateofbirth", "2000-10-10"),
new("surname", "Smith") },
"mock"));
_razorModel = new RazorModel()
{
PageContext = new PageContext
{
HttpContext = new DefaultHttpContext() { User = user }
}
};
}
I have a website which exposes an ODatas $metadata but to do anything further requires the request to be authenticated (using a cookie).
I want to access this from a console app, not a browser.
I am using Microsofts Odata V4 client code generator.
1) Create a wrapper around the provided Container created by the OData client code generator.
2) Log in and get the cookie you need for authentication
3) Add a hook to the request builder, so you can apply cookies at request time. For my app, I specifically needed the cookie with the name .AspNet.ApplicationCookie
Here is a full working example. You can instantiate this container with the user and password needed as defined at the bottom. This MUST match whatever the controller at the Login API is expecting.
using Nito.AsyncEx;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace MyAppOdataOdataService.Default
{
public class MyAppOdataContainer : Container
{
public Cookie[] _MyAppOdataAuthcookie;
public string cookieAuthName = ".AspNet.ApplicationCookie";
private string baseurl = "https://TheAppwWebsite.co.jp/";
public MyAppOdataContainer(MyAppOdataLoginInfo logininfo ) :
base(new Uri("https://TheAppwWebsite.co.jp/odata/"))
{
// init authorization
_MyAppOdataAuthcookie = AsyncContext.Run(() => AuthenticateUser(logininfo));
if (_MyAppOdataAuthcookie == null) throw new UnauthorizedAccessException();
this.BuildingRequest += AddCookie;
}
private void AddCookie(object sender, Microsoft.OData.Client.BuildingRequestEventArgs e)
{
e.Headers.Add("Cookie", cookieAuthName+"=" + _MyAppOdataAuthcookie.First(c=>c.Name == cookieAuthName).Value);
}
private async Task<Cookie[]> AuthenticateUser(MyAppOdataLoginInfo logininfo)
{
CookieContainer cookies = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookies;
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri(baseurl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Uri uri = new Uri(baseurl + "/Login/Login");
HttpResponseMessage response = await client.PostAsJsonAsync(uri, logininfo);
response.EnsureSuccessStatusCode();
// Return the URI of the created resource.
return cookies.GetCookies(uri).Cast<Cookie>().ToArray();
}
}
public class MyAppOdataLoginInfo
{
public string Username { get; set; }
public string Password { get; set; }
}
}
Thanks:
How to apply the cookie:
Creating the client code:
CookieContainer explaination:
Post operation idea here - for authorizing - having to use PostAsJsonAsync
I don't have much experience with .NET Web Api, but i've been working with it a while now, following John Papa's SPA application tutorial on Pluralsight. The application works fine, but the thing i'm struggling with now, is unit testing POST-controllers.
I have followed this incredible guide on how to unit test web api controllers. The only problem for me is when it comes to test the POST method.
My controller looks like this:
[ActionName("course")]
public HttpResponseMessage Post(Course course)
{
if (course == null)
throw new HttpResponseException(HttpStatusCode.NotAcceptable);
try
{
Uow.Courses.Add(course);
Uow.commit();
}
catch (Exception)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
var response = Request.CreateResponse(HttpStatusCode.Created, course);
string uri = Url.Link(routeName: "ControllerActionAndId",
routeValues: new { id = course.Id });
response.Headers.Location = new Uri(uri);
return response;
}
And my unit test looks like this:
[Test]
public void PostShouldReturnHttpResponse()
{
var populatedPostController = new CoursesController(new TestUOW());
SetupPostControllerForTest(populatedPostController);
var course = new Course
{
Id = 12,
Author = new UserProfile()
{
Firstname = "John",
Lastname = "Johnson",
},
Description = "Testcourse",
Title = "Test Title"
};
var responses = populatedPostController.Post(course);
ObjectContent content = responses.Content as ObjectContent;
Course result = (Course)content.Value;
Assert.AreSame(result, course);
}
With the help function:
public static void SetupPostControllerForTest(ApiController controller)
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/courses/course");
var route = config.Routes.MapHttpRoute(
name: "ControllerActionAndId",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" }
);
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "courses" }, { "action", "course" } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
}
When i debug the unit test, it seems to fail at:
string uri = Url.Link(routeName: "ControllerActionAndId",
routeValues: new { id = course.Id });
response.Headers.Location = new Uri(uri); //Exception because uri = null
It seems like the Url.Link can't find the route.
I tried this guide aswell, but i really want the example i have above to work.
Am i missing something really basic here?
Yes, you are missing the one line in the configuration as Nemesv mentioned.
controller.Request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData
As you can see, configuring a controller just for using the UrlHelper is extremely complex. I tend to avoid the use of UrlHelper in the controller classes for that reason. I usually introduce an external dependency to make testing easier like an IUrlHelper, which allows me to mock the behavior in an unit test.
public interface IUrlHelper
{
string Link(string routeName, object routeValues);
string Route(string routeName, object routeValues);
}
public class UrlHelperWrapper : IUrlHelper
{
UrlHelper helper;
public UrlHelperWrapper(UrlHelper helper)
{
this.helper = helper;
}
public string Link(string routeName, object routeValues)
{
return this.helper.Link(routeName, routeValues);
}
public string Route(string routeName, object routeValues)
{
return this.helper.Route(routeName, routeValues);
}
}
I inject this UrlHelperWraper in the real Web API, and a mock of the IUrlHelper interface in the tests. By doing that, you don't need all that complex configuration with the routes.
Regards,
Pablo.
I'm building a READ ONLY sencha-touch app for our local church.
We use Vimeo to host all of our videos, and I'd like to integrate our Vimeo vids as well as our RSS feed into our web app.
The rest of the "content" in the app will be static "info" as well as a contact form.
My question is, is it kosher to ONLY use ASP.NET MVC3 (minus the "V") to drive the JSON to our web app?
Yes, this works great. Just return a JsonResult.
Here is an example I am using in production:
public partial class StudentController : BaseController {
public StudentController(RESTContext portalContext)
: base(portalContext) { }
[HttpGet, Url("organizations/{organizationId?}/students")]
public virtual JsonResult List(Guid? organizationId) {
if (organizationId != RESTContext.OrganizationId)
throw new HttpNotAuthorizedException();
var query = RESTContext.GetQuery<IQuery<StudentCasesReport>>()
.Where(x => x.OrganizationId, organizationId)
.OrderBy(x => x.LastName, SortOrder.Ascending);
var cases = query.Execute(IsolationLevel.ReadUncommitted);
return Json(cases, JsonRequestBehavior.AllowGet);
}
[HttpGet, Url("organizations/{organizationId?}/students/{studentId?}")]
public virtual JsonResult Get(Guid? organizationId, Guid? studentId) {
if (studentId.IsNull())
throw new HttpNotFoundExecption();
if (organizationId != RESTContext.OrganizationId)
throw new HttpNotModifiedException();
var query = RESTContext.GetQuery<IQuery<StudentCasesReport>>()
.Where(x => x.OrganizationId, organizationId)
.Where(x => x.StudentCaseId, studentId)
.OrderBy(x => x.LastName, SortOrder.Ascending);
var cases = query.Execute(IsolationLevel.ReadUncommitted).FirstOrDefault();
if (cases.IsNull())
throw new HttpNotFoundExecption();
return Json(cases, JsonRequestBehavior.AllowGet);
}
}