2017-07-14 110 views
0

我一直在嘲笑IHttpContextAccessor一些Web API集成测试。我的目标是能够嘲笑IHttpContextAccessor并返回NameIdentifier声明和RemoteIpAddress。.NET核心测试 - 模拟IHttpContextAccessor与FakeItEasy

测试

public class InsertUser : TestBase 
{ 
    private UserController _userController; 

    [OneTimeSetUp] 
    public void OneTimeSetUp() 
    { 
     IStringLocalizer<UserController> localizer = A.Fake<IStringLocalizer<UserController>>(); 

     _userController = new UserController(localizer, Mapper, UserService, StatusService, IdentityService); 
     _userController.ControllerContext = A.Fake<ControllerContext>(); 
     _userController.ControllerContext.HttpContext = A.Fake<DefaultHttpContext>(); 

     var fakeClaim = A.Fake<Claim>(x => x.WithArgumentsForConstructor(() => new Claim(ClaimTypes.NameIdentifier, "1"))); 
     var fakeIdentity = A.Fake<ClaimsPrincipal>(); 

     A.CallTo(() => fakeIdentity.FindFirst(ClaimTypes.NameIdentifier)).Returns(fakeClaim); 
     A.CallTo(() => _userController.ControllerContext.HttpContext.User).Returns(fakeIdentity); 

     StatusTypeEntity statusType = ObjectMother.InsertStatusType(StatusTypeEnum.StatusType.User); 
     StatusEntity status = ObjectMother.InsertStatus(StatusEnum.Status.Active, statusType); 
     ObjectMother.InsertUser("FirstName", "LastName", "[email protected]", "PasswordHash", "PasswordSalt", status); 
    } 

    public static IEnumerable TestCases 
    { 
     get 
     { 
      //InsertUser_Should_Insert 
      yield return new TestCaseData(new InsertUserModel 
      { 
       FirstName = "FirstName", 
       LastName = "LastName", 
       StatusId = 1, 
       Email = "[email protected]" 
      }, 
       1, 
       2).SetName("InsertUser_Should_Insert"); 

      //InsertUser_Should_Not_Insert_When_StatusId_Not_Exist 
      yield return new TestCaseData(new InsertUserModel 
      { 
       FirstName = "FirstName", 
       LastName = "LastName", 
       StatusId = int.MaxValue, 
       Email = "[email protected]" 
      }, 
       1, 
       1).SetName("InsertUser_Should_Not_Insert_When_StatusId_Not_Exist"); 

      //InsertUser_Should_Not_Insert_When_Email_Already_Exist 
      yield return new TestCaseData(new InsertUserModel 
      { 
       FirstName = "FirstName", 
       LastName = "LastName", 
       StatusId = 1, 
       Email = "[email protected]" 
      }, 
       1, 
       1).SetName("InsertUser_Should_Not_Insert_When_Email_Already_Exist"); 
     } 
    } 

    [Test, TestCaseSource(nameof(TestCases))] 
    public async Task Test(InsertUserModel model, int userCountBefore, int userCountAfter) 
    { 
     //Before 
     int resultBefore = Database.User.Count(); 

     resultBefore.ShouldBe(userCountBefore); 

     //Delete 
     await _userController.InsertUser(model); 

     //After 
     int resultAfter = Database.User.Count(); 

     resultAfter.ShouldBe(userCountAfter); 
    } 
} 

控制器

[Route("api/administration/[controller]")] 
[Authorize(Roles = "Administrator")] 
public class UserController : Controller 
{ 
    private readonly IStringLocalizer<UserController> _localizer; 
    private readonly IMapper _mapper; 
    private readonly IUserService _userService; 
    private readonly IStatusService _statusService; 
    private readonly IIdentityService _identityService; 

    public UserController(IStringLocalizer<UserController> localizer, 
     IMapper mapper, 
     IUserService userService, 
     IStatusService statusService, 
     IIdentityService identityService) 
    { 
     _localizer = localizer; 
     _mapper = mapper; 
     _userService = userService; 
     _statusService = statusService; 
     _identityService = identityService; 
    } 

    [HttpPost("InsertUser")] 
    public async Task<IActionResult> InsertUser([FromBody] InsertUserModel model) 
    { 
     if (model == null || !ModelState.IsValid) 
     { 
      return Ok(new GenericResultModel(_localizer["An_unexpected_error_has_occurred_Please_try_again"])); 
     } 

     StatusModel status = await _statusService.GetStatus(model.StatusId, StatusTypeEnum.StatusType.User); 

     if (status == null) 
     { 
      return Ok(new GenericResultModel(_localizer["Could_not_find_status"])); 
     } 

     UserModel userExist = await _userService.GetUser(model.Email); 

     if (userExist != null) 
     { 
      return Ok(new GenericResultModel(_localizer["Email_address_is_already_in_use"])); 
     } 

     UserModel user = _mapper.Map<InsertUserModel, UserModel>(model); 

     var letrTryAndGetUserIdFromNameIdentifier = _identityService.GetUserId(); 

     user.DefaultIpAddress = _identityService.GetIpAddress(); 

     //UserModel insertedUser = await _userService.InsertUser(user, model.Password); 
     UserModel insertedUser = await _userService.InsertUser(user, "TODO"); 

     if (insertedUser != null) 
     { 
      return Ok(new GenericResultModel { Id = insertedUser.Id }); 
     } 

     return Ok(new GenericResultModel(_localizer["Could_not_create_user"])); 
    } 
} 

重要的线在这里是:

var letrTryAndGetUserIdFromNameIdentifier = _identityService.GetUserId(); 

IdentityService

public class IdentityService : IIdentityService 
{ 
    private readonly IHttpContextAccessor _httpContextAccessor; 

    public IdentityService(IHttpContextAccessor httpContextAccessor) 
    { 
     _httpContextAccessor = httpContextAccessor; 
    } 

    public int GetUserId() 
    { 
     if (_httpContextAccessor.HttpContext == null || !Authenticated()) 
     { 
      throw new AuthenticationException("User is not authenticated."); 
     } 

     ClaimsPrincipal claimsPrincipal = _httpContextAccessor.HttpContext.User; 

     string userIdString = claimsPrincipal.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; 
     int.TryParse(userIdString, out int userIdInt); 

     return userIdInt; 
    } 

    public string GetIpAddress()l 
    { 
     return _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress.ToString(); 
    } 
} 

失败在这里:

if (_httpContextAccessor.HttpContext == null || !Authenticated()) 
{ 
    throw new AuthenticationException("User is not authenticated."); 
} 

目前_httpContextAccessor.HttpContext总是空。我不知道如果我在这里在正确的道路上..

+0

创建一个模拟的HttpContext它由你的模拟httpContextAccessor返回,然后你将它传递给你的IdentityService fixture的构造函数 – Mardoxx

回答

1

对于这种测试,您最好写一个使用TestHost类型的集成测试,并尽可能少地模拟。它会简单得多,并且您将能够测试当前方法不支持的过滤器(如路由和授权规则)。你可以在这里阅读文档更多: https://docs.microsoft.com/en-us/aspnet/core/testing/integration-testing

我展示了如何编写API测试,在ASP.NET核心过滤器我的MSDN文章,这里的一部分,良好的样品: https://msdn.microsoft.com/en-us/magazine/mt767699.aspx

0

修改的测试项目

var userIdClaim = A.Fake<Claim>(x => x.WithArgumentsForConstructor(() => new Claim(ClaimTypes.NameIdentifier, "1"))); 

var httpContextAccessor = A.Fake<HttpContextAccessor>(); 
httpContextAccessor.HttpContext = A.Fake<HttpContext>(); 
httpContextAccessor.HttpContext.User = A.Fake<ClaimsPrincipal>(); 
IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); 
A.CallTo(() => httpContextAccessor.HttpContext.Connection.RemoteIpAddress).Returns(ipAddress); 
A.CallTo(() => httpContextAccessor.HttpContext.User.Identity.IsAuthenticated).Returns(true); 
A.CallTo(() => httpContextAccessor.HttpContext.User.Claims).Returns(new List<Claim> { userIdClaim }); 
var identityService = new IdentityService(httpContextAccessor); 
_userController = new UserController(localizer, Mapper, UserService, StatusService, identityService); 

我现在可以在控制器做到:

var claims = HttpContext.User.Claims.ToList(); 

而且身份服务:

ClaimsPrincipal claimsPrincipal = _httpContextAccessor.HttpContext.User; 

string userIdString = claimsPrincipal.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; 
int.TryParse(userIdString, out int userIdInt); 

return userIdInt; 

请让我现在,如果你认为有一个更好的华y用于伪装HttpContext。