30

我正在尝试在我的测试用例中自动模拟 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);
    }
}
4

3 回答 3

35

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

广义方法

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

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);
        }
    }
}

属性的 type 值HttpRequestMessageRequest使用以下自定义构建的:

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()));
    }
}

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

创建一个自定义组合如下 - 请注意AutoFixture 自定义的顺序很重要

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

希望有帮助。

于 2013-11-13T12:47:29.367 回答
9

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

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

如果UserControllerIUserModel通过它的构造函数接受一个,你可以选择那个构造函数(最贪婪的)来代替

更新

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>()));
    }
}

原始测试将执行良好。

于 2013-11-11T14:48:26.617 回答
2

根据 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>();
于 2017-01-25T15:08:35.247 回答