.NET CORE测试 - 使用FakeItEasy模拟IHttpContextAccessor

时间:2017-07-14 19:43:59

标签: c# .net asp.net-core integration-testing fakeiteasy

我坚持嘲笑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。我不确定我是否走在正确的道路上......

2 个答案:

答案 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有更好的方法,请现在就告诉我。