Mock an Admin Controller for Unit Testing - c#

I am working with ASP.NET Core 2.0, using xUnit and Moq to create unit tests for administrative functions. I have an AdminController.cs that uses dependency injection for the following within its constructor
private UserManager<AppUser> userManager;
private IUserValidator<AppUser> userValidator;
private IPasswordValidator<AppUser> passwordValidator;
private IPasswordHasher<AppUser> passwordHasher;
private RoleManager<IdentityRole> roleManager;
private SignInManager<AppUser> signInManager;
I try to arrange them in the following manner in my unit test
// Arrange
Mock<EFRepository> mockRepo = new Mock<EFRepository>();
var userStoreMock = new Mock<IUserRoleStore<AppUser>>();
var userManager = new UserManager<AppUser>(userStoreMock.Object, null, null, null, null, null, null, null, null);
AppUser user = new AppUser();
var roleStoreMock = new Mock<IRoleStore<IdentityRole>>();
var userValidator = new Mock<IUserValidator<AppUser>>();
var passwordValidator = new Mock<IPasswordValidator<AppUser>>();
var passwordHasher = new Mock<IPasswordHasher<AppUser>>();
var roleManager = new RoleManager<IdentityRole>(roleStoreMock.Object, null, null, null, null, null);
var signInManager = new Mock<SignInManager<AppUser>>();
//THIS LINE CAUSES THE ERROR
AdminController controller = new AdminController(userManager, userValidator.Object, passwordValidator.Object, passwordHasher.Object, roleManager, signInManager.Object);
I get the following error:
Can not instantiate proxy of class: Microsoft.AspNetCore.Identity.SignInManager
Could not find a parameterless constructor.
I have not yet been able to find a proper way of mocking a SignInManager that works
I've been trying to work with the method below without success:
private Mock<SignInManager<AppUser>> GetMockSignInManager()
{
var mockUsrMgr = GetMockUserManager();
var mockAuthMgr = new Mock<AuthenticationManager>();
var mockContextAssosor = new Mock<IHttpContextAccessor>();
var mockClaimsFactory = new Mock<IUserClaimsPrincipalFactory<AppUser>>();
//i am unclear on how to mock the options
var opts = new Mock<IOptions<>>();
var mockLogger = new Mock<ILogger<SignInManager<AppUser>>>();
//namespace for IAuthenicationSchemeProvider is not recognized
var scheme = new Mock<IAuthenticationSchemeProvider>();
//return new Mock<SignInManager<AppUser>>(mockUsrMgr.Object, mockAuthMgr.Object...and so on);
}

private Mock<SignInManager<AppUser>> GetMockSignInManager()
{
var mockUsrMgr = new UserManager<AppUser>(userStoreMock.Object, null, null, null, null, null, null, null, null);
var ctxAccessor = new HttpContextAccessor();
var mockClaimsPrinFact = new Mock<IUserClaimsPrincipalFactory<AppUser>>();
var mockOpts = new Mock<IOptions<IdentityOptions>>();
var mockLogger = new Mock<ILogger<SignInManager<AppUser>>>();
return new Mock<SignInManager<AppUser>>(mockUsrMgr.Object, ctxAccessor, mockClaimsPrinFact.Object, mockOpts.Object, mockLogger.Object);
}

Related

Mock the Request when testing a Razor Page

I need to create a unit test for this function that resides inside the HomeModel razor page
public async Task<IActionResult> OnGetCurrencyAsync(string currency, CancellationToken ct = default)
{
var returnUrl = Request.Headers["Referer"].ToString();
var path = new System.Uri(returnUrl).LocalPath;
if (string.IsNullOrWhiteSpace(path) || !Url.IsLocalUrl(path))
returnUrl = Url.Content("~/");
var session = await _currentUserService.GetOrInitializeSessionAsync(ct);
if (!currency.IsNullOrEmpty())
{
session.Currency = currency;
await _currentUserService.SetSession(session, ct);
}
return Redirect(returnUrl);
}
Till now I've created the following test
[Fact]
public async Task Test1()
{
var returnUrl = "https://localhost:44317/paris";
var currentuserService = new Mock<ICurrentUserService>();
var options = new Mock<IOptions<Core.Configuration.AppSettings>>();
var navigationMenu = new Mock<INavigationMenu>();
var productModelService = new Mock<IProductModelService>();
var userSavedProductRepository = new Mock<IUserSavedProductRepository>();
var userSavedProductService = new Mock<IUserSavedProductService>();
var homePage = new HomeModel(currentuserService.Object, options.Object, navigationMenu.Object, productModelService.Object, userSavedProductService.Object, userSavedProductRepository.Object);
var res = await homePage.OnGetCurrencyAsync("EUR", CancellationToken.None);
Assert.IsType<RedirectResult>(res);
var redirectResult = (RedirectResult)res;
Assert.True(returnUrl == redirectResult.Url);
}
But when I execute it, I got that .Request is null..
How can I correctly set it up?
The PageContext of the subject PageModel needs a HttpContext that contains the desired Request setup to satisfy the subject under test.
Reference: Razor Pages unit tests in ASP.NET Core: Unit tests of the page model methods
//Arrange
var returnUrl = "https://localhost:44317/paris";
//...code omitted for brevity
// use a default context to have access to a request
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Referer"] = returnUrl; //<--
//these are needed as well for the page context
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
// need page context for the page model
var pageContext = new PageContext(actionContext) {
ViewData = viewData
};
//create model with necessary dependencies applied
var homePage = new HomeModel(currentuserService.Object, options.Object, navigationMenu.Object, productModelService.Object, userSavedProductService.Object, userSavedProductRepository.Object) {
PageContext = pageContext, //<--
Url = new UrlHelper(actionContext)
};
//Act
//...omitted for brevity

Moq usermanager missing IUserEmailStore

what i am testing.
This is an identity server project with a login to federated gateway. I do not control this gateway and am having issues with them not returning the proper claims back to me that i need to verify the users logins. I would like to be able to test that i can handle these errors.
For example email claim is missing without that i can not login a user.
I have created a test that tests the email claim is missing returns an error.(Works fine)
Now I am trying to test the other side of things. If the claims are in fact there it should return the user that matches to the claims returned.
The method we are testing
public static async Task<(ApplicationUser user, string provider, string providerUserUserName, IEnumerable<Claim> claims, string message)> FindUserFromExternalProvider(AuthenticateResult result, UserManager<ApplicationUser> userManager, ILogger<SegesExternalController> logger)
{
var externalUser = result.Principal;
// try to determine the unique id of the external user (issued by the provider)
var eMailClaim = externalUser.FindFirst(SegesSettingsConstants.SegesEmailClaimName);
if(eMailClaim == null) return (null, null, null, null, $"{SegesSettingsConstants.SegesEmailClaimName} claim not found.");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.LogSegesClaims(logger);
claims.Remove(eMailClaim);
// Should we remove more claims
var provider = result.Properties.Items["scheme"];
var providerUserUserName = eMailClaim.Value;
var user = await userManager.FindByEmailAsync(providerUserUserName); // Test Breaks here
return (user, provider, providerUserUserName, claims, null);
}
Test
[Fact]
public async void Federated_login_with_email_claim_return_no_error()
{
// Arrange
var principal = new ClaimsPrincipal();
principal.AddIdentity(new ClaimsIdentity(
new Claim[] {
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "Testbruger til André"),
new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", #"PROD\Salg43"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode", "8200"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality", "Aarhus N"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "test#email.com"),
},
"FakeScheme"));
var authenticateResult = AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties() { Items = { { "scheme", "fed" } } }, "FakeScheme"));
var exprectUser = new ApplicationUser()
{
UserName = "test#email.com",
NormalizedUserName = "TEST#EMAIL.COM",
NormalizedEmail = "TEST#EMAIL.COM",
Email = "test#email.com",
Id = 123,
EmailConfirmed = true
};
var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
var mockQueryableUserStore = new Mock<IQueryableUserStore<ApplicationUser>>();
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null, null, null, null, null, null);
var logger = new Logger<ExternalController>(new LoggerFactory());
// Act
var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManager, logger);
// Assert
user.ShouldNotBeNull();
}
The issue with above.
I am trying to moq a usermanager for my unit test
var exprectUser = new ApplicationUser()
{
UserName = "test#email.com",
NormalizedUserName = "TEST#EMAIL.COM",
NormalizedEmail = "TEST#EMAIL.COM",
Email = "test#email.com",
Id = 123,
EmailConfirmed = true
};
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null, null, null, null, null, null);
however when the method i am testing tries to find the user.
var findUser = await userManager.FindByEmailAsync("test#test.com");
it throws an error
Message: System.NotSupportedException : Store does not implement IUserEmailStore.
How do i implement IUserEmailStore in my moq usermanager?
My unit test project does contain the newest EntityFramework package.
Trying another way.
var founduser = userManager.Users.FirstOrDefault(e => e.Email.Equals("test#test.com", StringComparison.InvariantCultureIgnoreCase));
results in
System.NotSupportedException : Store does not implement IQueryableUserStore.
I think i must be moqing this wrong.
Update From comment
Ok i can moq the IUserEmailStore but I am not sure what i should do with it
var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
I managed to create a full moq usermanager that lets me search on email
public class MoqUserManager : UserManager<ApplicationUser>
{
public MoqUserManager(IUserStore<ApplicationUser> userStore) : base(userStore,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<ApplicationUser>>().Object,
new IUserValidator<ApplicationUser>[0],
new IPasswordValidator<ApplicationUser>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
new Mock<ILogger<UserManager<ApplicationUser>>>().Object)
{ }
public override Task<ApplicationUser> FindByEmailAsync(string email)
{
return Task.FromResult(new ApplicationUser { Email = email });
}
}
which gives me
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new FakeUserManager(mockUserStore.Object);
So now i can verify that the proper user is returned from my identity server matching the federated login user.
Okay your with the updated question the issue lies in
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null, null, null, null, null, null);
This is not creating a mock, but an actual instance of UserManager<T>.
You will have to do
var userManagerMock = new Mock<UserManager<ApplicationUser>>(mockUserStore.Object, null, null, null, null, null, null, null, null);
then do an setup
userManagerMock.Setup(um => um.FindByEmailAsync("test#email.com)).Returns(exprectUser)
and pass userManagerMock.Object to your
var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManagerMock.Object, logger);
When mocking, you never want to call new on the external dependency and instead mock it, since then you can't change its behavior for a specific test. UserManager<T> should have all or most public properties as virtual, so you can override them.

Throw ExpectedException TimeoutException with Stub is not working c#

I have this unit test, and I've been trying to throw an exception on it, but I'm not able to do it, please can you help me?
[TestMethod]
[ExpectedException(typeof(TimeoutException))]
public async Task HandleAsyncDeleteModel_WhenRepositoryFails_ThrowsException()
{
//Arrange
var token = new CancellationToken();
var deleteModel = new DeleteProcessCommand(_img, _tnt, _pro, _url);
var writeRepository = new StubIWriteRepository<Dto>()
{
DeleteIfExistsAsyncGuidGuidGuidCancellationToken = (img, tnt, pro, tkn) =>
{
throw new TimeoutException();
}
};
var Logger = new StubILogger();
var commandHandler = new CommandHandler(Logger, writeRepository, null, null, null, null, null, null);
//Act
await commandHandler.HandleAsync(deleteModel, token);
}
Unit tests do not wait for async methods. No one is calling for the results of the async method. You need to do a .Wait on it to force it to wait for a result.
[TestMethod]
[ExpectedException(typeof(TimeoutException))]
public async Task HandleAsyncDeleteModel_WhenRepositoryFails_ThrowsException()
{
//Arrange
var token = new CancellationToken();
var deleteModel = new DeleteProcessCommand(_img, _tnt, _pro, _url);
var writeRepository = new StubIWriteRepository<Dto>()
{
DeleteIfExistsAsyncGuidGuidGuidCancellationToken = (img, tnt, pro, tkn) =>
{
throw new TimeoutException();
}
};
var Logger = new StubILogger();
var commandHandler = new CommandHandler(Logger, writeRepository, null, null, null, null, null, null);
//Act
commandHandler.HandleAsync(deleteModel, token).Wait();
}

Correct mock for Http/Controller context for a razor helper test unit

I have an issue trying to create a unit test for testing a custom razor helper (that using the kendo grid razor helper).
The problems comes from the kendo grid helper, which try to access the following property : HtmlHelper.ViewContext.Controller.ValueProvider (and throws a NullReferenceException. This exception comes from the access to the ValueProvider property of the controller context).
I concluded that my "mock" http/controller context is not correctly initialized. I try several solutions found on the internet for mocking the context, but I still have the problem).
This is my current unit test code:
ModelMock model = ...
ViewDataDictionary vd = new ViewDataDictionary(model);
var htmlHelper = CreateHtmlHelper<ModelMock>(vd);
string htmlResult = htmlHelper.PopinGrid(m => m.JsonList).ToHtmlString(); // This is the call of my helper.
... (asserts)
And my helpers for context mocking (they work with other simple unit tests for razor helpers):
public static HtmlHelper<T> CreateHtmlHelper<T>(ViewDataDictionary viewDataDictionary)
where T : new()
{
Mock<ControllerBase> controller = new Mock<ControllerBase>();
Mock<ControllerContext> controllerContext = new Mock<ControllerContext>(
new Mock<HttpContextBase>().Object,
new RouteData(),
controller.Object);
Mock<ViewContext> viewContext = new Mock<ViewContext>(
controllerContext.Object,
new Mock<IView>().Object,
viewDataDictionary,
new TempDataDictionary(),
new StringWriter(CultureInfo.InvariantCulture));
Mock<IViewDataContainer> mockViewDataContainer = new Mock<IViewDataContainer>();
bool unobtrusiveJavascriptEnabled = false;
bool clientValidationEnabled = true;
viewContext.SetupGet(c => c.UnobtrusiveJavaScriptEnabled).Returns(unobtrusiveJavascriptEnabled);
viewContext.SetupGet(c => c.FormContext).Returns(new FormContext { FormId = "myForm" });
viewContext.SetupGet(c => c.ClientValidationEnabled).Returns(clientValidationEnabled);
viewContext.SetupGet(c => c.ViewData).Returns(viewDataDictionary);
// I add the following line because the "Controller" property of the viewContext was null (strange, given that I initialize the view context with the controller context).
viewContext.SetupGet(c => c.Controller).Returns(controller.Object);
mockViewDataContainer.Setup(v => v.ViewData).Returns(viewDataDictionary);
HttpContext.Current = FakeHttpContext();
return new HtmlHelper<T>(viewContext.Object, mockViewDataContainer.Object);
}
public static HttpContext FakeHttpContext()
{
HttpRequest httpRequest = new HttpRequest(string.Empty, "http://mockurl/", string.Empty);
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
HttpResponse httpResponse = new HttpResponse(stringWriter);
HttpContext httpContext = new HttpContext(httpRequest, httpResponse);
HttpSessionStateContainer sessionContainer = new HttpSessionStateContainer(
"id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10,
true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc,
false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
return httpContext;
}
Is anyone know the good solution for mocking a full http/controller context ?
Thanks !

Can't get IValueProvider values from QueryStringValueProvider(ControllerContext) in Unit Testing?

Here is the Test method Creating a mock for request and context
have added the querystring to the context and while debugging the TestMethod could able to see the values of the querystring collection
[TestMethod]
public void Save_Tester()
{
//Arrange
HomeController controller = new HomeController();
string querystring = "?key1=value1&key2=value2&key3=value3&key4=value4";
Mock<HttpRequestBase> mock_request = MockHelpers.CreateMockRequest(querystring);
Mock<HttpContextBase> mock_context = new Mock<HttpContextBase>();
//Request
NameValueCollection myValues = new NameValueCollection();
FormCollection formcollection = new FormCollection(myValues);
mock_request.SetupGet(mr => mr.Params).Returns(myValues);
mock_request.SetupGet(mr => mr.Form).Returns(myValues);
mock_request.SetupGet(mr => mr.QueryString).Returns(HttpUtility.ParseQueryString(querystring));
//Context
mock_context.Setup(c => c.Request).Returns(mock_request.Object);
controller.ValueProvider = formcollection.ToValueProvider();
// Act
Assert.IsNotNull(controller); //Guard
var result_query = controller.Save() as ViewResult;
// Assert
}
In the save method using QueryStringValueProvider to get the Values but has no namevaluecollection
QueryStringValueProvider takes the ControllerContext.HttpContext.Request.QueryString that to avail in debugging
[HttpPost]
public ActionResult Save()
{
try
{
IValueProvider provider = new QueryStringValueProvider(this.ControllerContext);
//provider has no namevaluecollection values
}
catch
{
throw;
}
}
Solution
This solved it I should have taken some time to analyse before putting a question
Uri uri = new Uri("http://localhost?key1=value1&key2=value2&key3=value3&&key4=value4");
HttpRequest httpRequest = new HttpRequest(string.Empty, uri.ToString(),
uri.Query.TrimStart('?'));
HttpContext httpContext = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
HttpSessionStateContainer sessionContainer = new HttpSessionStateContainer("id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10, true, HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
SessionStateUtility.AddHttpSessionStateToContext(
httpContext, sessionContainer);
HttpContext.Current = httpContext;

Categories

Resources