【问题标题】:Unit Testing a Web API controller which Has Been Authorized with JWT对已通过 JWT 授权的 Web API 控制器进行单元测试
【发布时间】:2018-09-18 11:48:49
【问题描述】:

我正在尝试在 Web API 控制器下进行单元测试。

    [IdentityBasicAuthentication]
    [Authorize]
    [HttpPost]
    public HttpResponseMessage GetOrderByQR([FromBody]string id)
    {
        try
        {
            var identity = HttpContext.Current.User.Identity as ClaimsIdentity;

            var user = UserManager().IdentifyToken(identity);

            _orderManager.CheckOrderQRFraud(id);

            return Request.CreateResponse(HttpStatusCode.OK, _orderManager.GetSingleOrderWithUserPaymentAccountBy(user.UserID, id));
        }
        catch (BusinessException ex)
        {
            return CreateRawStringResponse(HttpStatusCode.NotFound, ex.Message);
        }
    }

但下面的测试方法在IdentifyToken() 上抛出空异常,因为没有当前用户,我明白了。

    [TestMethod]
    public void GetOrderByQR_Returns_OK_On_Successful_Request()
    {
        string orderID = "4B854B3D-397E-425F-AEAF-00F7B4110021";

        var testResponse = _orderController.GetOrderByQR(orderID);

        Assert.AreEqual(HttpStatusCode.OK, testResponse.StatusCode);
    }

在寻找答案时,我发现解决方案在于模拟此授权属性。所以,虽然我安装了这个Moq 包,但我还是无法像刚开始单元测试一样成功地运行测试。

下面是IdentityToken()方法,如果你也需要检查一下。

    public User IdentifyToken(ClaimsIdentity identity)
    {
        string userEmail = "";
        string userPassword = "";

        if (identity != null)
        {
            IEnumerable<Claim> claims = identity.Claims;
            List<Claim> claimsArray = claims.ToList();
            string[] emailArray = claimsArray[0].ToString().Split(':');
            string emailValue = emailArray[2].ToString();
            userEmail = emailValue.Trim();
            string[] passwordArray = claimsArray[1].ToString().Split(':');
            string passwordValue = passwordArray[2].ToString();
            userPassword = passwordValue.Trim();
        }

        var user = base.GetSingleBy(x => x.Email == userEmail && x.Password == userPassword);

        return user;
    }

我应该如何编写我的测试方法?提前谢谢!

编辑:

Manager类实例如下。

public class OrderController : BaseController
{
    OrderManager _orderManager;

    public OrderController()
    {
        _orderManager = new OrderManager();
    }

    //Order Controllers
}

【问题讨论】:

  • 在单独进行单元测试时,您是如何通过HttpContext 的?控制器如何安排测试?提供一个minimal reproducible example,可以更好地了解实际问题。
  • 我想我无法在单元测试中通过HttpContext,这是我的问题。另外,您在第二个问题中的意思不清楚。我在[TestClass] 的顶部执行此操作:private readonly OrderController _orderController; public OrderTests() { _orderController = new OrderController { Request = new System.Net.Http.HttpRequestMessage(), Configuration = new System.Web.Http.HttpConfiguration() }; }
  • 这个问题缺少太多细节,我无法有效地帮助您。提供minimal reproducible example
  • 显示的代码存在设计问题。 HttpContext 在单元测试中将为空。我不知道用户管理器或订单管理器来自哪里......等等。没有适当的细节,就做不了什么。
  • 好吧,我不是专业人士,但我不明白你为什么要求查看 ordermanager/usermanager 来自哪里。我需要伪造单元测试,就好像我使用的是经过身份验证的用户一样。人们使用模拟来解决这类问题,但我找不到离开 Moq 的方法。我编辑了我的问题以显示经理课程的来源。

标签: c# unit-testing asp.net-web-api authorization moq


【解决方案1】:

您可以使用 TestServer 来测试您的 API 方法

 using (var server = TestServer.Create<Startup>())
    {
        var result = await server.HttpClient.GetAsync("api/Orders/id");
        string responseContent = await result.Content.ReadAsStringAsync();
        var entity = JsonConvert.DeserializeObject<List<Orders>>(responseContent);
        // other code
    }

【讨论】:

    【解决方案2】:

    又花了一天时间详细了解单元测试后,我发现我需要分别处理(换句话说测试)每个方法。如果我没记错的话,这也是单元测试名称的来源。

    所以,首先,我测试我的这个UserManager().IdentifyToken(identity);方法如下。

        [TestMethod]
        public void IdentifyToken_Returns_UserWM_On_Successful_Request()
        {
            UserManager userManager = new UserManager();
    
            MD5Hasher passwordHasher = new MD5Hasher();
    
            IEnumerable<Claim> claims = new List<Claim>
            {
                new Claim(ClaimTypes.Email, "creditmaster@admin.com"),
                new Claim(ClaimTypes.Hash, passwordHasher.Encrypt("qW12345?"))
            };
    
            ClaimsIdentity identity = new ClaimsIdentity();
            identity.AddClaims(claims);
    
            var testResult = userManager.IdentifyToken(identity);
    
            Assert.AreEqual(typeof(UserWM), testResult.GetType());
        }
    

    收到成功的测试响应后,我继续_orderManager.GetSingleOrderWithUserPaymentAccountBy(user.UserID, id));,如下图。

        [TestMethod]
        public void GetSingleOrderWithUserPaymentAccountBy_Returns_OrderWM_On_Successful_Request()
        {
            Random rnd = new Random();
            int randomUser = rnd.Next(0, 690);
            int randomOrder = rnd.Next(0, 40);
    
            OrderManager orderManager = new OrderManager();
            UserManager userManager = new UserManager();
    
            List<UserWM> userList = userManager.GetAllUser();
            var user = userList[randomUser];
    
            List<OrderWM> orderList = orderManager.GetAllOrder();
            var order = orderList[randomOrder];
            string orderCode = "HHBI5OBFWG5WDSKP";
    
            var testResult = orderManager.GetSingleOrderWithUserPaymentAccountBy(user.UserID, orderCode);
    
            Assert.AreEqual(typeof(OrderWM), testResult.GetType());
        }
    

    最后,如果你能成功地分别对你所有的方法进行单元测试,那么当它们被同时调用时,它们就会成功地工作..

    我还想分享下面的测试方法,我在其中对我的方法(名称是 InsertUser)进行单元测试,该方法在所有与用户实体相关的强制属性按预期填充后将用户插入数据库。

    棘手的部分是,这个InsertUser 包含太多if-else 验证控件,例如,

            if (string.IsNullOrEmpty(user.FirstName))
            {
                throw new BusinessException(JsonResponse.CreateBusinessExceptionResponse(ErrorMessageConstants.UserFirstnameEmptyError), "");
            }
    

    由于这里讨论的代码行太多,我不希望您在它们周围迷失,因此不分享整个Insert User

    如果您在单元测试中也遇到过类似情况,那么您唯一应该做的就是将这些验证收集到另一个方法中(我将其命名为 InsertUserValidation)。所以,现在我们有两种不同的方法:InsertUserInsertUserValidation

    现在您可能会问为什么我们必须将验证从主要方法中分离出来。就我而言,我有如下所示的与时间相关的验证。

            if (activationRequest == null)
            {
                throw new BusinessException(JsonResponse.CreateBusinessExceptionResponse(ErrorMessageConstants.UserActivationCodeNotRequested), "");
            }
            else if (activationRequest.CreationTime.AddMinutes(3) < DateTime.Now)
            {
                throw new BusinessException(JsonResponse.CreateBusinessExceptionResponse(ErrorMessageConstants.UserActivationCodeIsTimedOut), "");
            }
    

    想一想,由于两个Unit Test 之间的时间段不明确,您需要将这些验证放入不同的方法中,因此您可以按照下面的测试方法中的描述模拟它们。例如,也许我运行了我的相关方法,它在五分钟前创建了 activationRequest,而我现在运行了我的 InsertUser 方法。如果我没有分开这个验证,InsertUser 仍然会包含上面的验证,它会抛出异常,因为它在调用时已经超过了三分钟。

        [TestMethod]
        public void InsertUser_Returns_UserWM_On_Successful_Request()
        {
            UserManager userManager = new UserManager();
    
            MD5Hasher passwordHasher = new MD5Hasher();
    
            Random rnd = new Random();
            int dummyEmailName = rnd.Next(0, 700);
            string dummyEmail = dummyEmailName.ToString() + "@gmail.com";
    
            UserActivationRequest userActivationRequest = new UserActivationRequest
            {
                EMail = dummyEmail,
                ActivationCode = "444855",
                IsUsed = false,
                IsActive = true,
                ConfirmationType = 1,
                ReadCount = 0
            };
    
            UserCreateAccountWM userCreateAccountWM = new UserCreateAccountWM()
            {
                FirstName = "Unit",
                LastName = "Test",
                Email = dummyEmail,
                Password = passwordHasher.Encrypt("yC123456?"),
                CountryID = 1,
                PhoneCountryCode = 90,
                MobileNumber = "5327894512",
                ActivationCode = "444855"
            };
    
            var validationMock = new Mock<UserManager>();
            validationMock.CallBase = true;
            validationMock.Setup(x => x.InsertUserValidation(It.IsAny<UserCreateAccountWM>())).Returns(userActivationRequest); //if your validations do not return anything back, just forget about the part with .Returns()
    
            var testResult = validationMock.Object.InsertUser(userCreateAccountWM);
    
            Assert.AreEqual(typeof(UserWM), testResult.GetType());
        }
    

    是的,如果我没记错的话,你还是需要创建相关实体。

    在我完成之前,我使用了 Moq 框架,并且不要忘记您要分离的方法仍然应该在同一个命名空间下。

    希望我对所有这些方面的新手有所帮助。

    【讨论】:

    • 你的答案不完整:什么是 MD5Hasher,我从哪里引用它?我认为在你的第一块代码之后,其余的都是离题且无用的。请考虑提供针对特定问题的完整解决方案,并跳过那些对主题没有价值的部分。
    • @LeonardoX MD5Hasher 是我将纯文本转换为加密文本的类。如果我共享所有这些不相关的功能,我的回答将毫无用处。在复制粘贴整个代码块之前先动动脑筋!
    • 你的答案是不可验证的,因为没有提供所有的代码,至少你应该提供 MD5Hasher.Encrypt 方法。另外,正如其他人所指出的,您提供了不必要的细节,正如我在之前的评论中所说,您的问题与测试 jwt 身份验证令牌有关,而不是“插入用户”或“验证用户”功能,这与主题,所以再次,请,我鼓励你重新制定你的答案
    猜你喜欢
    • 2017-06-02
    • 1970-01-01
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多