【问题标题】:Test AsycAction in C#在 C# 中测试 AsycAction
【发布时间】:2014-07-30 20:53:05
【问题描述】:

我在下面有这段代码,关于一个负责处理异常并记录它们的类。

using ProReserve.Reserve.Domain.Licenciados;
using ProReserve.Reserve.Domain.Sistema.Logging;
using ProReserve.Reserve.Domain.Usuarios;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http.Controllers;

namespace ProReserve.Reserve.API.Filters
{
    public class DefaultControllerActionInvoker : ApiControllerActionInvoker
    {
        private Func<HttpRequestMessage, ILoggingService> _getLoggingService;

        public DefaultControllerActionInvoker(Func<HttpRequestMessage, ILoggingService> getLoggingService)
        {
            _getLoggingService = getLoggingService;
        }

        public override async Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            Task<HttpResponseMessage> actionTask = base.InvokeActionAsync(actionContext, cancellationToken); // (*)

            if (actionTask.Exception != null &&
                actionTask.Exception.GetBaseException() != null &&
                actionContext.Request.Properties["Licenciado"] as Licenciado != null)
            {
                var exception = actionTask.Exception.GetBaseException();

                Operacao operacao = this.getHttpStatusCode(actionTask.Result); 

                await SaveLogAsync(exception, actionContext.Request, operacao);

                return await Task.Run(() => new HttpResponseMessage(actionTask.Result.StatusCode)
                {
                    Content = new StringContent(exception.Message),
                    ReasonPhrase = "Error"
                });
            }
            return await actionTask;
        }

        private async Task SaveLogAsync(Exception exception, HttpRequestMessage request, Operacao operacao)
        {
            var guidLog = string.Format("{0}{1}", DateTime.Now.Ticks, Thread.CurrentThread.ManagedThreadId);
            var requestInfo = string.Format("{0} {1}", request.Method, request.RequestUri);

            var httpContext = request.Properties["MS_HttpContext"] as HttpContextBase;
            var remoteAddr = httpContext.Request.ServerVariables["REMOTE_ADDR"];
            var serverName = httpContext.Request.ServerVariables["SERVER_NAME"];
            var logonUser = httpContext.Request.ServerVariables["LOGON_USER"];
            var usuario = request.Properties["Usuario"] as Usuario;
            var message = string.Format("{0} {1}", exception.Message.ToString(), exception.InnerException != null ? exception.InnerException.ToString() : string.Empty);

            var logEntry = new Log(guidLog, operacao)
            {
                IDUsuario = usuario.ID,
                IPCliente = remoteAddr,
                IPServidor = serverName,
                MaquinaCliente = logonUser,
                Mensagem = string.Format(@"RequestInfo: {0} - Error: {1}", requestInfo, message),
            };

            using (var loggingService = _getLoggingService.Invoke(request))
            {
                loggingService.Licenciado = (Licenciado)request.Properties["Licenciado"];
                await Task.Run(() => loggingService.Inserir(logEntry));
            }
        }

        private Operacao getHttpStatusCode(HttpResponseMessage response)
        {
            Operacao operacao = Operacao.Response;

            if (response.StatusCode != System.Net.HttpStatusCode.OK)
            {
                switch (response.StatusCode)
                {
                    case System.Net.HttpStatusCode.BadRequest: //400
                        operacao = Operacao.BadGateway;
                        break;
                    case System.Net.HttpStatusCode.Unauthorized: //401
                        operacao = Operacao.Unauthorized;
                        break;
                    case System.Net.HttpStatusCode.PaymentRequired: //402
                        operacao = Operacao.PaymentRequired;
                        break;
                    case System.Net.HttpStatusCode.Forbidden: //403
                        operacao = Operacao.Forbidden;
                        break;
                    case System.Net.HttpStatusCode.NotFound: //404
                        operacao = Operacao.NotFound;
                        break;
                    case System.Net.HttpStatusCode.MethodNotAllowed: //405
                        operacao = Operacao.MethodNotAllowed;
                        break;
                    case System.Net.HttpStatusCode.NotAcceptable: //406
                        operacao = Operacao.NotAcceptable;
                        break;
                    case System.Net.HttpStatusCode.ProxyAuthenticationRequired: //407
                        operacao = Operacao.ProxyAuthenticationRequired;
                        break;
                    case System.Net.HttpStatusCode.RequestTimeout: //408
                        operacao = Operacao.RequestTimeout;
                        break;
                    case System.Net.HttpStatusCode.Conflict: //409
                        operacao = Operacao.Conflict;
                        break;
                    case System.Net.HttpStatusCode.Gone: //410
                        operacao = Operacao.Gone;
                        break;
                    case System.Net.HttpStatusCode.LengthRequired: //411
                        operacao = Operacao.LengthRequired;
                        break;
                    case System.Net.HttpStatusCode.PreconditionFailed: //412
                        operacao = Operacao.PreconditionFailed;
                        break;
                    case System.Net.HttpStatusCode.RequestEntityTooLarge: //413
                        operacao = Operacao.RequestEntityTooLarge;
                        break;
                    case System.Net.HttpStatusCode.RequestUriTooLong: //414
                        operacao = Operacao.RequestUriTooLong;
                        break;
                    case System.Net.HttpStatusCode.UnsupportedMediaType: //415
                        operacao = Operacao.UnsupportedMediaType;
                        break;
                    case System.Net.HttpStatusCode.RequestedRangeNotSatisfiable: //416
                        operacao = Operacao.RequestedRangeNotSatisfiable;
                        break;
                    case System.Net.HttpStatusCode.ExpectationFailed: //417
                        operacao = Operacao.ExpectationFailed;
                        break;
                    case System.Net.HttpStatusCode.InternalServerError: //500
                        operacao = Operacao.InternalServerError;
                        break;
                    case System.Net.HttpStatusCode.NotImplemented: //501
                        operacao = Operacao.NotImplemented;
                        break;
                    case System.Net.HttpStatusCode.BadGateway: //502
                        operacao = Operacao.BadGateway;
                        break;
                    case System.Net.HttpStatusCode.ServiceUnavailable: //503
                        operacao = Operacao.ServiceUnavailable;
                        break;
                    case System.Net.HttpStatusCode.GatewayTimeout: //504
                        operacao = Operacao.GatewayTimeout;
                        break;
                    case System.Net.HttpStatusCode.HttpVersionNotSupported: //505
                        operacao = Operacao.HttpVersionNotSupported;
                        break;
                    default:
                        operacao = Operacao.Response; //51
                        break;
                }
            }

            return operacao;
        }
    }
}

在 (*) 行 - Task&lt;HttpResponseMessage&gt; actionTask = base.InvokeActionAsync(actionContext, cancellationToken);) - 我有一个命令可以拦截异步操作调用。变量actionTask 返回一些属性。我需要填充其中两个属性,而不是 null。它们是:

  • 结果 -> 获取返回的状态码
  • 异常 -> 获取返回的异常

我需要加载这两个属性,但我不能在它们都不为空的情况下进行单元测试。在某些测试中,Result 不为 null,而在其他测试中,Exception 不为 null,但两者都不会。

下面是我的单元测试:

[TestMethod]
public void ActionAsync_Com_Exception()
{
    // Arrange
    Log log = null;

    _loggingService.SetupSet(l => l.Licenciado = _licenciado);
    _loggingService.Setup(a => a.Inserir(It.IsAny<Log>()))
        .Callback<Log>((l) =>
        {
            log = l;
        });

    //Task<object> task1 = Task<object>.Factory.StartNew(() => new Exception()); //(3)

    Task<object> task1 = Task<object>.Factory.StartNew(() =>
    {
        object myClass = new object();
        return myClass;
    });

    //_actionDescriptor.Setup(a => a.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<IDictionary<string, object>>(), It.IsAny<CancellationToken>()))
    //    .Throws(new Exception("exception"));   //(1)

    _actionDescriptor.Setup(a => a.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<IDictionary<string, object>>(), It.IsAny<CancellationToken>()))
        .Returns(task1); //(2)

    DefaultControllerActionInvoker actionInvoker = new DefaultControllerActionInvoker(r => _loggingService.Object);

    // Act
    HttpResponseMessage response = actionInvoker.InvokeActionAsync(_baseActionContext, CancellationToken.None).Result;

    // Assert
    Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);
    Assert.AreEqual("Error", response.ReasonPhrase);

    Assert.AreEqual("127.0.0.1", log.IPCliente);
    Assert.AreEqual("0.0.0.0", log.IPServidor);
    Assert.AreEqual("USER\\MACHINE", log.MaquinaCliente);
    Assert.AreEqual("RequestInfo: POST http://localhost/teste - Error: exception ", log.Mensagem);

    _loggingService.VerifySet(l => l.Licenciado = _licenciado);
    _loggingService.Verify(a => a.Inserir(It.IsAny<Log>()));
    _loggingService.Verify(a => a.Dispose());
}

在我的测试中我尝试过:

(1) 在这种情况下,它返回一个异常,但 Result 属性为空。

(2) 在这种情况下,它返回一个结果,但 Exception 属性为 null。在这种情况下,我还需要以某种方式进行模拟,以返回一些错误状态码(40x 或 50x)。但我不知道该怎么办。

(3) 我已经尝试创建一个触发异常的任务,但进入了上面的情况 (2)

所以,我不能让我的单元测试也正常工作。

我需要在我的单元测试代码中做些什么来加载这两个属性?

【问题讨论】:

  • 支持异步测试,标记为async Task而不是void

标签: c# unit-testing mocking async-await moq


【解决方案1】:

不确定我是否在关注您的代码中发生的事情,但最重要的是您正在进行异步完成,您的测试断言不会等待。这意味着断言在异步代码执行的同时或之前执行,因此它们不会测试结果。

标准测试框架没有充分支持异步/等待,所以如果你调用一个返回任务的方法,你必须等待任务 - 基本上让你的测试代码同步......给定你什么已经完成了,我认为多出一行就可以了:

// Act
HttpResponseMessage response = actionInvoker.InvokeActionAsync(_baseActionContext, CancellationToken.None).Result;

// After you have obtained the returned Task object,
// and done whatever will initiate the asynchronous activity, wait for it to complete...
task1.Wait();  // <-- Try this

// Assert
Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-27
    • 2012-08-27
    • 1970-01-01
    • 2011-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多