I am using JustMock and I am having difficulty having JustMock work with Entity Framework and mocking the database activity - specifically a fake insert
I want to do a fake insert and then return the fake inserted entity. I want to test that the password is hashed before insert (handled by the BLL).
UserBLL
public UserBLL(MyDatabaseEntities context)
: base(context)
{
}
public void Create(string email, string password)
{
//check and make sure that the email does not already exist
User user = GetUser(email);
if (user != null)
throw new Exception("Email account already exists");
string salt = GetSalt();
var hashedPassword = HashPassword(password, salt);
var newUser = new User()
{
EmailAddress = email,
Password = hashedPassword
};
context.Users.Add(newUser);
context.SaveChanges();
}
Here are my tests...
//This works
[Test]
[ExpectedException(ExpectedMessage = "Email account already exists")]
public void Create_NoDupeEmails()
{
List<User> fakeUsers = new List<User>();
fakeUsers.Add(new User { EmailAddress = "peter#email.com", FirstName = "Peter", LastName = "Griffin" });
fakeUsers.Add(new User { EmailAddress = "lois#email.com", FirstName = "Lois", LastName = "Griffin" });
//arrange
var context = Mock.Create<MyDatabaseEntities>();
Mock.Arrange(() => context.Users).ReturnsCollection(fakeUsers);
//act
var bll = new UserBLL(context);
bll.Create("peter#email.com", "password");
//Assert
//handled by the expected exception
}
Failing test
[Test]
public void Create_PasswordHashed()
{
var context = Mock.Create<MyDatabaseEntities>();
//arrange
Mock.Arrange(() => context.SaveChanges()).MustBeCalled();
var bll = new UserBLL(context);
bll.Create("test#email.com", "password");
User user = bll.GetUser("test#email.com");
//user is null?
Assert.IsTrue(user != null); //it fails here
Assert.IsTrue(user.Password != "password");
}
What am I doing wrong?
So I figured it out after looking at some samples across the web. The BLL remains the same, but here is the test
Once failing - now passing test
[Test]
public void Create_PasswordHashed()
{
//arrange
var context = Mock.Create<MyDatabaseEntities>();
var users = new List<User>();
Mock.Arrange(() => context.Users.Add(Arg.IsAny<User>()))
.DoInstead((User u) => users.Add(u)); //this will add the "user u" to the list of "users"
var bll = new UserBLL(context);
bll.Create("test#email.com", "password");
Assert.IsTrue(users.Count == 1);
Assert.IsTrue(!string.IsNullOrWhiteSpace(users[0].Password));
Assert.IsTrue(users[0].Password != "password");
}
Related
This is my controller code and I need to mock GetTokenDetails() method to conduct XUnit test on DecodeToken function. Am I doing in right way or not?
[HttpGet]
[Authorize(Roles = "Admin")]
[Route("DecodeToken")]
public IActionResult DecodeToken()
{
if (ModelState.IsValid)
{
var tokenResult = GetTokenDetails();
var result = _employeeService.ServiceDecodeToken(tokenResult.UserName, tokenResult.Role);
if (result.Httpcode == 200)
{
return Ok(result);
}
else
{
return StatusCode(500, result);
}
}
else
return BadRequest();
}
public GetTokenDetailsDto GetTokenDetails()
{
var token = HttpContext.Request.Headers["Authorization"].ToString();
var tokenbearer = token.Split(' ');
var handler = new JwtSecurityTokenHandler();
var decodedtoken = handler.ReadJwtToken(tokenbearer[1]);
string user = decodedtoken.Claims.Where(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name").FirstOrDefault().ToString();
string role = decodedtoken.Claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").FirstOrDefault().ToString();
var usr = user.Split(":");
var rol = role.Split(":");
string userName = usr[2].Trim();
string userRole = rol[2].Trim();
GetTokenDetailsDto getTokenDetailsDto = new GetTokenDetailsDto()
{
UserName = userName,
Role = userRole,
};
return getTokenDetailsDto;
}
First, Generally, I don't think there is a right way to do something.
There are multiple right ways to do it :). You just need to pick one or create one.
Second, I suggest you to create a new Dotnet standard Project under the same Solution and separate the logic from controllers. This way, you can create a Unit test project and import only the logic project.
Third, I see some points in your code that you are reading some value from the context( which is not avaiable in testing environment). For example, HttpContext.Request.Headers["Authorization"]. these kind of data should be in the input arguments of the function GetTokenDetails, so you can provide some sample data to test in your UnitTestProject. Something like this:
public GetTokenDetailsDto GetTokenDetails(string token)
{
var tokenbearer = token.Split(' ');
var handler = new JwtSecurityTokenHandler();
var decodedtoken = handler.ReadJwtToken(tokenbearer[1]);
string user = decodedtoken.Claims.Where(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name").FirstOrDefault().ToString();
string role = decodedtoken.Claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role").FirstOrDefault().ToString();
var usr = user.Split(":");
var rol = role.Split(":");
string userName = usr[2].Trim();
string userRole = rol[2].Trim();
GetTokenDetailsDto getTokenDetailsDto = new GetTokenDetailsDto()
{
UserName = userName,
Role = userRole,
};
return getTokenDetailsDto;
}
I am trying to implement on a Blazor-Server side application a simple login against LDAP server and use cookie to store user claims. I have the MainLayout set to Authorized, if the user is not authenticated it will be re-direct to Login page. I have already tested the LDAP connection and it works properly, the problem is no matter what I do the cookie doesn't get created in the browser. When I run the POST command I see the HttpStatusCode.OK but the cookie it's not created and the browser re-direct again to login page of course.
Can someone please tell me what am I doing wrong? My code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
AuthenticationController.cs
[ApiController]
public class AuthenticationController : Controller
{
[HttpPost]
[Route("authentication/login")]
public async Task<ActionResult> Login([FromBody]UserCredentials credentials)
{
string path = "LDAP://serveraddress.xxx";
try
{
using DirectoryEntry entry = new(path, credentials.Username, credentials.Password);
using DirectorySearcher searcher = new(entry);
searcher.Filter = $"(&(objectclass=user)(objectcategory=person)(samaccountname={credentials.Username}))";
var result = searcher.FindOne();
if (result != null)
{
List<Claim> claims = new();
claims.Add(new Claim(ClaimTypes.Name, credentials.Username));
//Get Groups
ResultPropertyCollection fields = result.Properties;
foreach (var group in result.Properties["memberof"])
{
var distinguishedName = new X500DistinguishedName(group.ToString());
var commonNameData = new AsnEncodedData("CN", distinguishedName.RawData);
var commonName = commonNameData.Format(false);
if (!string.IsNullOrEmpty(commonName))
{
claims.Add(new Claim(ClaimTypes.Role, commonName));
}
}
//Get Emails
foreach (var email in result.Properties["mail"])
{
claims.Add(new Claim(ClaimTypes.Email, email.ToString()));
}
ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme);
AuthenticationProperties authProperties = new()
{
AllowRefresh = true,
IssuedUtc = DateTime.Now,
ExpiresUtc = DateTimeOffset.Now.AddDays(1),
IsPersistent = true,
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
return Ok();
}
else
{
return NotFound("User Not Found!");
}
}
catch (Exception)
{
return NotFound("Login credentials is incorrect!");
}
}
[HttpPost]
[Route("authentication/logout")]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Ok();
}
}
Login.razor
#page "/login"
#page "/login/{ErrorMessage}"
#layout CenteredBlockLayout
#attribute [AllowAnonymous]
<MudPaper Elevation="25" Class="pa-8" Width="100%" MaxWidth="500px">
<MudItem><img src="/images/logo.svg" alt="Logo" style="width:400px; height:50px;" /></MudItem>
<MudText Typo="Typo.h4" GutterBottom="true">Sign In</MudText>
<MudTextField #bind-Value="#Username" T="string" Label="Username"/>
<MudTextField #bind-Value="#Password" T="string" Label="Password"/>
<MudButton OnClick="(() => PerformLoginAsync())">Sign In</MudButton>
</MudPaper>
#if (!string.IsNullOrEmpty(ErrorMessage))
{
<MudAlert Severity="Severity.Error">#ErrorMessage</MudAlert>
}
Login.razor.cs
public partial class Login
{
public string Username { get; set; }
public string Password { get; set; }
[Parameter]
public string ErrorMessage { get; set; }
[Inject]
HttpClient Client { get; set; }
[Inject]
private NavigationManager NavMan { get; set; }
private async Task PerformLoginAsync()
{
if (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password))
{
UserCredentials cred = new UserCredentials
{
Username = Username,
Password = Password
};
var serialized = JsonConvert.SerializeObject(cred);
var stringContent = new StringContent(serialized, Encoding.UTF8, "application/json");
using var result = await Client.PostAsync($"NavMan.BaseUri}authentication/login", stringContent);
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
NavMan.NavigateTo("/", true);
}
else
{
ErrorMessage = await result.Content.ReadAsStringAsync();
}
}
}
}
I believe you need to append the cookie to the response. I haven't tested this with your code but it should work something like this:
HttpContext.Response.Cookies.Append("my_cookie", claimsString, new CookieOptions()
{
Domain = "mydomain.com",
SameSite = SameSiteMode.Lax,
Secure = true,
Path = "/",
Expires = DateTime.UtcNow.AddDays(1)
}
(These cookie options are just an example, of course. Tailor them to your specific needs.)
Keep in mind that you'll need to convert your claims to a string so that you can store it as the value in a cookie. In our case we store claims in a JWT, so that's what gets stored in the cookie. Here's how I do it:
public string CreateJWT(HttpContext httpContext, User user)
{
var handler = new JwtSecurityTokenHandler();
var descriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim(ClaimTypes.Name, $"{user.FirstName} {user.LastName}"),
new Claim(ClaimTypes.Email, user.Email),
}),
Expires = DateTime.UtcNow.AddMinutes(Config.AccessExpMins),
Issuer = Config.Issuer,
Audience = Config.Audience,
SigningCredentials = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256)
};
var token = handler.CreateJwtSecurityToken(descriptor);
var accessToken = handler.WriteToken(token);
httpContext.Response.Cookies.Append("my_cookie", accessToken, new CookieOptions()
{
Domain = Config.CookieDomain,
SameSite = SameSiteMode.Lax,
Secure = true,
Path = "/",
Expires = DateTime.UtcNow.AddMinutes(Config.AccessExpMins)
});
return accessToken;
}
As for parsing the JWT, I'm sure there are a number of ways to go about it. The one that worked for me was this one.
I am using testing my Identity actions in my .NET Core web application but keep running into problems. I recently found a way to create a mocked usermanager without running into problems with its parameters, but then a new error came to me that I can't find any solutions too: "System.TypeLoadException: Could not load type 'Microsoft.EntityFrameworkCore.Query.Internal.IAsyncQueryProvider'"
Here is my relevant code:
Setting up the mock usermanager:
var _userManager = new Mock<FakeUserManager>();
UserIdentity user1 = new UserIdentity() { Id = UserId1, UserName = "test#gmail.com", Score = 5 };
UserIdentity user2 = new UserIdentity() { Id = UserId2, UserName = "pragim#gmail.com", Score = 1 };
UserIdentity user3 = new UserIdentity() { Id = UserId3, UserName = "ajax#gmail.com", Score = 0 };
UserIdentity user4 = new UserIdentity() { Id = UserId4, UserName = "pim#gmail.com", Score = 4 };
List<UserIdentity> users = new List<UserIdentity>() { user1, user2, user3, user4 };
var mock = users.AsQueryable().BuildMock();
_userManager.Setup(x => x.Users).Returns(mock.Object);
var identityRepository = new IdentityRepository(_userManager.Object, null, null);
_identityService = new IdentityService(identityRepository);
FakeUserManager.cs:
public class FakeUserManager : UserManager<UserIdentity>
{
public FakeUserManager()
: base(new Mock<IUserStore<UserIdentity>>().Object,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<UserIdentity>>().Object,
new IUserValidator<UserIdentity>[0],
new IPasswordValidator<UserIdentity>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
new Mock<ILogger<UserManager<UserIdentity>>>().Object)
{ }
}
Test method:
[TestMethod()]
public async Task GetUserAsyncTest()
{
//Arrange
//Act
var user = await _identityService.GetUserAsync(UserId4);
//Assert
Assert.AreEqual("pim#gmail.com", user.UserName);
}
Does anyone know a bypass / solution to my problem?
UPDATE: Stack trace:
EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
EntityFrameworkQueryableExtensions.SingleOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
IdentityRepository.GetUserAsync(Nullable`1 userId) line 55
IdentityService.GetUserAsync(Nullable`1 id) line 172
IdentityServiceTests.GetUserAsyncTest() line 102
ThreadOperations.ExecuteWithAbortSafety(Action action)
and the method GetUserAsync in the repository:
public async Task<UserIdentity> GetUserAsync(Guid? userId)
{
return await _userManager.Users.Where(x => x.Id.Equals(userId)).SingleOrDefaultAsync();
}
Though I am pretty late but after struggling with this error thought should post my bit too.
In my case, a package MockQueryable.Moq(https://www.nuget.org/packages/MockQueryable.Moq/) was being used, upgrading which solved this problem.
The _users sequence needs a provider that implements IAsyncQueryProvider. Couple of ways you can do it, the following is a mocked implementation.
Firstly, some scaffolding based on your OP:
public class UserIdentity : IdentityUser<Guid>
{
public int Score { get; set; }
}
public class IdentityRepository
{
readonly UserManager<UserIdentity> _userManager;
public IdentityRepository(UserManager<UserIdentity> userManager)
{
_userManager = userManager;
}
public async Task<UserIdentity> GetUserAsync(Guid? userId)
{
return await _userManager.Users.SingleOrDefaultAsync(x => x.Id.Equals(userId));
}
}
Create your mocked IAsyncQueryProvider and add it to your own IQueryable<UserIdentity>:
var user1 = new UserIdentity() { Id = Guid.NewGuid(), UserName = "foo#bar.com", Score = 1 };
var user2 = new UserIdentity() { Id = Guid.NewGuid(), UserName = "bar#baz.com", Score = 2 };
var user3 = new UserIdentity() { Id = Guid.NewGuid(), UserName = "tony stark", Score = 3000 };
var dataSource = new List<UserIdentity> { user1, user2, user3 }.AsQueryable();
var providerMock = new Mock<IAsyncQueryProvider>();
providerMock.Setup(x => x.ExecuteAsync<Task<UserIdentity>>(It.IsAny<Expression>(), It.IsAny<CancellationToken>()))
.Returns((Expression providedExpression, CancellationToken providedCancellationToken) => Task.FromResult(dataSource.Provider.Execute<UserIdentity>(providedExpression)));
var usersMock = new Mock<IQueryable<UserIdentity>>();
usersMock.Setup(x => x.ElementType).Returns(dataSource.ElementType);
usersMock.Setup(x => x.Expression).Returns(dataSource.Expression);
usersMock.Setup(x => x.Provider).Returns(providerMock.Object);
...
userManagerMock.Setup(x => x.Users).Returns(() => usersMock.Object);
You need a way to set the IQueryable<T> provider, the above is just one way you can do it.
If we pull this all together into a working LINQPad sample:
void Main()
{
var user1 = new UserIdentity() { Id = Guid.NewGuid(), UserName = "foo#bar.com", Score = 1 };
var user2 = new UserIdentity() { Id = Guid.NewGuid(), UserName = "bar#baz.com", Score = 2 };
var user3 = new UserIdentity() { Id = Guid.NewGuid(), UserName = "tony stark", Score = 3000 };
var dataSource = new List<UserIdentity> { user1, user2, user3 }.AsQueryable();
var providerMock = new Mock<IAsyncQueryProvider>();
providerMock.Setup(x => x.ExecuteAsync<Task<UserIdentity>>(It.IsAny<Expression>(), It.IsAny<CancellationToken>()))
.Returns((Expression providedExpression, CancellationToken providedCancellationToken) => Task.FromResult(dataSource.Provider.Execute<UserIdentity>(providedExpression)));
var usersMock = new Mock<IQueryable<UserIdentity>>();
usersMock.Setup(x => x.ElementType).Returns(dataSource.ElementType);
usersMock.Setup(x => x.Expression).Returns(dataSource.Expression);
usersMock.Setup(x => x.Provider).Returns(providerMock.Object);
var userManagerMock = new Mock<UserManager<UserIdentity>>
(new Mock<IUserStore<UserIdentity>>().Object,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<UserIdentity>>().Object,
new IUserValidator<UserIdentity>[0],
new IPasswordValidator<UserIdentity>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
new Mock<ILogger<UserManager<UserIdentity>>>().Object);
userManagerMock.Setup(x => x.Users).Returns(() => usersMock.Object);
var identityRepository = new IdentityRepository(userManagerMock.Object);
var result = identityRepository.GetUserAsync(user2.Id).Result;
Console.WriteLine(result);
}
public class UserIdentity : IdentityUser<Guid>
{
public int Score { get; set; }
}
public class IdentityRepository
{
readonly UserManager<UserIdentity> _userManager;
public IdentityRepository(UserManager<UserIdentity> userManager)
{
_userManager = userManager;
}
public async Task<UserIdentity> GetUserAsync(Guid? userId)
{
return await _userManager.Users.SingleOrDefaultAsync(x => x.Id.Equals(userId));
}
}
we get the desired result:
I've left out your IdentityService in the above as essentially it's a detail to the answer; you just need to get a working, mocked UserManager. For my mock libraries, in particular EntityFrameworkCore.Testing, I'd use a concrete IAsyncQueryProvider rather than a mock; follow the link for an example should you want to go down that path.
I am trying to test for failure conditions of my Account controller. When i run the test in debug mode, i am not seeing an expected result. I am expecting to return a failed identity result when reach the line of code to create a user async. however, in debug mode, it does not contain the error i provide it, and the success property is true. according to this site: https://www.symbolsource.org/MyGet/Metadata/aspnetwebstacknightly/Project/Microsoft.AspNet.Identity.Core/2.0.0-rtm-140226/Release/Default/Microsoft.AspNet.Identity.Core/Microsoft.AspNet.Identity.Core/IdentityResult.cs?ImageName=Microsoft.AspNet.Identity.Core, the way i am going about this it "should" work.
what is the right way to setup this test so that when i hit UserManager.CreateAsync, it will return a Failed IdentityResult?
Test i am trying to run
[TestMethod]
public async Task AccountController_Post_register_valid_model_account_creation_fails_returns_exception_result()
{
// arrange
RegisterApiModel model = new RegisterApiModel
{
BusinessType = BusinessType.Architect,
City = "asdf",
CompanyName = "asdf",
Email = "asdf#asdf.com",
FirstName = "asdf",
JobTitle = "asdf",
LastName = "asdf",
OperatingDistance = 123,
Phone = "1231231234",
Password = "12345678",
PostalCode = "asdf",
PrimaryContactName = "asdf",
PrimaryContactPhone = "1231231234",
PrimaryContactTitle = "asdf",
StateId = 2
};
// create http request
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost.com/api/Account/Register");
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "Companies" } });
// mock userstore
Mock<IUserStore<ApplicationUser>> userStore = new Mock<IUserStore<ApplicationUser>>();
userStore.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>())).Returns(Task.FromResult(IdentityResult.Failed("Name " + model.Email + " already exists")));
var passwordManager = userStore.As<IUserPasswordStore<ApplicationUser>>();
ApplicationUserManager um = new ApplicationUserManager(userStore.Object);
um.PasswordValidator = pwValidator;
AccountController controller = new AccountController(um);
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
// act
var result = await controller.Register(model);
// assert
result.ShouldBeType(typeof(ExceptionResult));
}
web api method i am trying to test
public async Task<IHttpActionResult> Register([FromBody]RegisterApiModel model)
{
try
{
var company = new Company
{
Name = model.CompanyName,
CreateDate = DateTime.Now,
SubscriptionStatus = SubscriptionStatus.Free,
Address1 = model.Address1 ?? string.Empty,
Address2 = model.Address2 ?? string.Empty,
City = model.City,
StateId = model.StateId,
PostalCode = model.PostalCode,
BusinessType = model.BusinessType.Value,
OperatingDistance = model.OperatingDistance.Value,
Phone = PhoneNumber.ToStorage(model.Phone),
Fax = model.Fax == null ? string.Empty : PhoneNumber.ToStorage(model.Fax),
PrimaryContactName = model.PrimaryContactName,
PrimaryContactPhone = PhoneNumber.ToStorage(model.PrimaryContactPhone),
PrimaryContactTitle = model.PrimaryContactTitle
};
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName, Company = company, JobTitle = model.JobTitle };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// make user a company admin
user.Claims.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim { ClaimValue = "Admin", ClaimType = "http://bidchuck.com/company/role", UserId = user.Id });
result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Link("Default", new { controller = "Account", action = "ConfirmEmail", userId = user.Id, code = code });
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking this link: link");
return Ok();
}
}
return BadRequest(result.Errors.First());
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
First of all, you are looking on source of Identity 2.2-alpha1 - it is not released yet. Better get decompiler (I use DotPeek from Jetbrains) and decompile assemblies you use in your project.
Then you are trying to test on too high level. Extract your method into class that is independent from your controllers:
UserService
{
public IdentityResult CreateUser(RegisterApiModel model, String urlCallback)
{
// don't forget to add generated code and userId as parameters into url
// do your user creation.
}
}
In your controller call this service:
public async Task<IHttpActionResult> Register([FromBody]RegisterApiModel model)
{
var urlCallbac = Url.Link("Default", new { controller = "Account", action = "ConfirmEmail" });
var result = await userService.CreateUserAsync(model, urlCallback);
if (result.Succeeded)
{
return Ok();
}
return BadRequest(result.Errors.First());
}
And test user Service separately from controllers. Your tests will become much more simple.
And at the moment it is very difficult to say why you are getting this result. Probably mocks are not completely set up to do what's needed to be done.
I'm trying to get a unit test working for a service that is injecting items into the IHttpRequest.Items, using a request filter:
this.RequestFilters.Add((req, res, dto) =>
{
// simplified for readability...
var repo = container.Resolve<IClientRepository>();
var apiKey = req.Headers["ApiKey"];
// lookup account code from api key
var accountcode = repo.GetByApiKey(apiKey);
req.Items.Add("AccountCode", accountCode);
});
My service uses that dictionary item:
public class UserService : AppServiceBase
{
public IUserServiceGateway UserServiceGateway { get; set; }
public object Any(UserRequest request)
{
var accountCode = base.Request.Items["AccountCode"].ToString();
var user = UserServiceGateway.GetUserByUsername(request.Name);
return new UserResponse { User = user };
}
}
My test needs be somehow to mock the request, and insert that account code item:
[Test]
public void ValidUsernameReturnUser()
{
// arrange
var gateway = new Mock<IUserServiceGateway>();
gateway.Setup(s => s.GetUserByUsername(It.IsAny<string>()))
.Returns(new UserAccountDTO { Forename = "John", Surname = "Doe" });
var service = new UserService {
UserServiceGateway = gateway.Object,
RequestContext = new MockRequestContext(),
//Request = has no setter
};
// request is this case is null
base.Request.Items.Add("AccountCode", "DEF456");
// act
var response = (UserResponse)service.Any(new UserRequest { Name = "test" });
// assert
Assert.That(response.Result, Is.Not.Null);
}
The service itself accepts a mocked RequestContext, but not a Request. The test therefore fails. Is there a better way to do this?
I think this should do it.
[Test]
public void ValidUsernameReturnUser()
{
// arrange
var mockRequestContext = new MockRequestContext();
//add items to Request
mockRequestContext.Get<IHttpRequest>().Items.Add("AccountCode", "DEF456");
var gateway = new Mock<IUserServiceGateway>();
gateway.Setup(s => s.GetUserByUsername(It.IsAny<string>()))
.Returns(new UserAccountDTO { Forename = "John", Surname = "Doe" });
var service = new UserService {
UserServiceGateway = gateway.Object,
RequestContext = new MockRequestContext(),
};
// act
var response = (UserResponse)service.Any(new UserRequest { Name = "test" });
// assert
Assert.That(response.Result, Is.Not.Null);
}