已对JWT授权的Web API控制器进行单元测试

时间:2018-09-18 11:48:49

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

我正在尝试在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
}

2 个答案:

答案 0 :(得分:0)

您可以使用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
    }

答案 1 :(得分:-1)

花了一天的时间来了解有关单元测试的更多信息之后,我发现我需要分别对待(换句话说就是测试)每种方法。如果我没记错的话,那也是单元测试名称的来源。

因此,首先,我如下测试我的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框架,并且不要忘记您要分离的方法应该仍在同一命名空间下。

希望对所有这些新手有帮助。