我坚持嘲笑IHttpContextAccessor进行一些web api集成测试。我的目标是能够模拟IHttpContextAccessor并返回NameIdentifier声明和RemoteIpAddress。
测试
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@Email.Email", "PasswordHash", "PasswordSalt", status);
}
public static IEnumerable TestCases
{
get
{
//InsertUser_Should_Insert
yield return new TestCaseData(new InsertUserModel
{
FirstName = "FirstName",
LastName = "LastName",
StatusId = 1,
Email = "Email2@Email.Email"
},
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 = "Email2@Email.Email"
},
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@Email.Email"
},
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始终为null。我不确定我是否走在正确的道路上......
答案 0 :(得分:4)
对于这种测试,您最好编写一个使用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
答案 1 :(得分:3)
修改后的测试项目
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;
如果您认为伪造HttpContext有更好的方法,请现在就告诉我。