Автономный контроллер Web Api 2

Я пытаюсь автоматически смоделировать класс 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();
            })
            );
    }
}

UsersController:

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

3 ответа

Решение

Примечание. Для первоначального ответа требуется скопировать одну и ту же настройку для каждого нового 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);
        }
    }
}

Значение типа HttpRequestMessage, для Request свойство, строится с использованием следующей настройки:

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

Надеюсь, это поможет.

Примечание:Предполагая, что UserController класс занимает IUserModel через его конструктор.

Как это выглядит, конструктор по умолчанию ApiController выполняет некоторую работу (возможно, больше, чем простые задания).

Если UserController класс занимает IUserModel через его конструктор вы можете выбрать этот конструктор (самый жадный).

Обновление:

Заменить 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>()));
    }
}

И оригинальный тест выполнится нормально.

На основании ответа Никоса:

Это более общий способ использования этой настройки, где можно указать тип контроллера и 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>();
Другие вопросы по тегам