【问题标题】:Automocking Web Api 2 controller自动模拟 Web Api 2 控制器
【发布时间】:2013-11-11 14:09:56
【问题描述】:

我正在尝试在我的测试用例中自动模拟 ApiController 类。当我使用 WebApi1 时它工作得很好。我开始在新项目上使用 WebApi2,但在尝试运行新测试后抛出了这个异常:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Security.Cryptography.CryptographicException: pCertContext is an invalid handle.
   at System.Security.Cryptography.CAPI.CertSetCertificateContextProperty(SafeCertContextHandle pCertContext, UInt32 dwPropId, UInt32 dwFlags, SafeLocalAllocHandle safeLocalAllocHandle)
   at System.Security.Cryptography.X509Certificates.X509Certificate2.set_Archived(Boolean value)

我的测试代码:

[Theory, AutoMoqData]
public void approparte_status_code_is_returned(
    string privateKey,
    UsersController sut)
{
    var response = sut.GetUser(privateKey);
    var result = response;

    Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}

如果我手动创建 sut,测试用例确实有效:

[Theory, AutoMoqData]
public void approparte_status_code_is_returned(
    string privateKey,
    [Frozen]Mock<IUserModel> stubModel)
{
    var sut = new UsersController(stubModel.Object);
    var response = sut.GetUser(privateKey);
    var result = response;

    Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}

在尝试模拟 ControllerContext.RequestContext.ClientCertificate 时似乎出了点问题我试图在没有它的情况下创建一个夹具(使用 AutoFixture .Without() 方法),但随后甚至旧测试也开始失败。

我的 AutoMoqDataAttribute:

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(new Fixture()
            .Customize(new WebApiCustomization()))
    {
    }
}

WebApi 自定义:

public class WebApiCustomization : CompositeCustomization
{
    public WebApiCustomization() 
        : base(
        new HttpRequestMessageCustomization(),
        new AutoMoqCustomization())
    {
    }
}

HttpRequestMessage 自定义:

public class HttpRequestMessageCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<HttpRequestMessage>(c => c
            .Without(x => x.Content)
            .Do(x =>
            {
                x.Properties[HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();
            })
            );
    }
}

用户控制器:

/// <summary>
/// Handles user's account. 
/// </summary>
[RoutePrefix("api/v1/users/{privateKey:length(64)}")]
public class UsersController : ApiController
{
    private readonly IUserModel _model;

    public UsersController(IUserModel model)
    {
        _model = model;
    }

    /// <summary>
    /// Returns a user.
    /// </summary>
    /// <param name="privateKey">The private key of the user.</param>
    /// <returns>
    /// 200 (OK) with user data is returned when user is found.
    /// 404 (Not found) is returned when user is not found.
    /// </returns>
    [HttpGet]
    [Route("")]
    public HttpResponseMessage GetUser(string privateKey)
    {
        UserProjection projection;

        try
        {
            projection = new UserProjection(_model.Get(privateKey));
        }
        catch (UserNotFoundException)
        {
            return new HttpResponseMessage(HttpStatusCode.NotFound);
        }

        return Request.CreateResponse(HttpStatusCode.OK, projection);
    }
}

【问题讨论】:

标签: c# autofixture automoq


【解决方案1】:

注意: 原始的answer 需要为每个新的 ApiController 复制相同的自定义。

广义方法

另一种方法是自动填充所有 ApiController 上的 Request 属性(从而使您免于剪切、复制和粘贴):

internal class ApiControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(
            new FilteringSpecimenBuilder(
                new Postprocessor(
                    new MethodInvoker(
                        new ModestConstructorQuery()),
                    new ApiControllerFiller()),
                new ApiControllerSpecification()));
    }

    private class ApiControllerFiller : ISpecimenCommand
    {
        public void Execute(object specimen, ISpecimenContext context)
        {
            if (specimen == null)
                throw new ArgumentNullException("specimen");
            if (context == null)
                throw new ArgumentNullException("context");

            var target = specimen as ApiController;
            if (target == null)
                throw new ArgumentException(
                    "The specimen must be an instance of ApiController.", 
                    "specimen");

            target.Request =
                (HttpRequestMessage)context.Resolve(
                    typeof(HttpRequestMessage));
        }
    }

    private class ApiControllerSpecification : IRequestSpecification
    {
        public bool IsSatisfiedBy(object request)
        {
            var requestType = request as Type;
            if (requestType == null)
                return false;
            return typeof(ApiController).IsAssignableFrom(requestType);
        }
    }
}

Request 属性的 HttpRequestMessage 类型的值是使用以下自定义构建的:

internal class HttpRequestMessageCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<HttpRequestMessage>(c => c
            .Without(x => x.Content)
            .Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
                new HttpConfiguration()));
    }
}

将所有内容打包到一个复合定制中

如下创建自定义组合 - 请注意 the order of AutoFixture Customizations matter:

internal class ApiControllerConventions : CompositeCustomization
{
    internal ApiControllerConventions()
        : base(
            new HttpRequestMessageCustomization(),
            new ApiControllerCustomization(),
            new AutoMoqCustomization())
    {
    }
}

希望对您有所帮助。

【讨论】:

【解决方案2】:

注意 假设UserController 类通过其构造函数获取IUserModel

看起来,ApiController 的默认构造函数执行了一些工作(可能不仅仅是简单的赋值)。

如果UserController 类通过其构造函数获取IUserModelyou can pick that constructor (the greediest) instead

更新

HttpRequestMessageCustomization 自定义替换为:

internal class ApiControllerCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<HttpRequestMessage>(c => c
            .Without(x => x.Content)
            .Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
                new HttpConfiguration()));

        fixture.Customize<UsersController>(c => c
            .OmitAutoProperties()
            .With(x => x.Request, fixture.Create<HttpRequestMessage>()));
    }
}

原来的测试会执行得很好。

【讨论】:

  • 感谢 Nikos 的快速重播。我已经添加了您建议的自定义设置,但是当我尝试执行测试时仍然抛出相同的异常。
  • 您是否将HttpRequestMessageCustomization 替换为建议的ApiControllerCustomization?如果是(并且您仍然有同样的异常)UsersController 类的外观如何?
  • 我尝试先将它添加到HttpRequestMessageCustomization 旁边。然后我尝试用ApiControllerCustomization 替换它。在这两种情况下我都有同样的例外。
  • 建议自定义目标类是否派生来自ApiController
  • @user2910739 实际上解决方案是在几个small iterations 之后提出的@类。
【解决方案3】:

根据 Nikos 的回答:

这是使用此自定义的更通用方式,其中可以提供控制器类型,Customization 可用于任何控制器

internal class WebApiCustomization<TControllerType> : ICustomization
    where TControllerType : ApiController
    {
        public void Customize(IFixture fixture)
        {
            fixture.Customize<HttpRequestMessage>(c => c
                .Without(x => x.Content)
                .Do(x => x.Properties[HttpPropertyKeys.HttpConfigurationKey] =
                    new HttpConfiguration()));

            fixture.Customize<TControllerType>(c => c
                .OmitAutoProperties()
                .With(x => x.Request, fixture.Create<HttpRequestMessage>()));
}
}

然后按如下方式使用:

var fixture = new Fixture().Customize(
    new WebApiCustomization<UsersController>());
var sut = fixture.Create<UsersController>();

【讨论】:

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