模拟属性取决于HttpRequest

时间:2018-12-06 14:44:48

标签: c# asp.net-core dependency-injection inversion-of-control asp.net-core-webapi

我正在进行以下设置:

public class ExampleBaseController : Microsoft.AspNetCore.Mvc.Controller
{
    public UserDetails UserDetails => Request.GetUserDetailsFromHttpHeaders();
}


public class ExampleConcreteController : ExampleBaseController
{
    // UserDetails is being used in here
    // this is the class under test

我需要能够在生产运行期间注入UserDetails,还必须能够在测试期间对其进行模拟。 由于UserDetails依赖于Request,并且Request是Microsoft.AspNetCore.Mvc.Controller的成员,所以我不知道该如何实现。

3 个答案:

答案 0 :(得分:2)

如果您想模拟某些东西,则应首先允许模拟。如果要模拟UserDetails,则应允许在其getter上进行模拟,并在新建合同中传递所需的上下文:

public class ExampleBaseController : Microsoft.AspNetCore.Mvc.Controller
{
    private readonly IUserDetailsProvider _userDetailsProvider;
    public UserDetails UserDetails => _userDetailsProvider.Get(Request);

    public ExampleBaseController(IUserDetailsProvider userDetailsProvider)
    {
        _userDetailsProvider = userDetailsProvider;
    }
}

因此,在测试中,您模拟IUserDetailsProvider以返回一些“ foobar”。在生产环境中,您只需调用GetUserDetailsFromHttpHeaders()内部传递的Request方法。

回答有关请求和控制器关系的问题。 Controller取决于Request,是的,Microsoft认为将它们牢固地合并在一起而不是传递依赖关系将是一件好事,例如:

public class FooBarController : Microsoft.AspNetCore.Mvc.Controller
{
    private readonly System.Web.HttpRequestBase _request;

    public FooBarController(System.Web.HttpRequestBase request)
    {
        _request = request;
    }
}

甚至是这样:

public class FooBarController : Microsoft.AspNetCore.Mvc.Controller
{
    public void ProcessRequest(System.Web.HttpRequestBase request)
    {
        //request here
    }
}

他们改为使用属性注入,这使开发人员无法影响注入。这是个问题。但并非无法解决-如果您需要将那些耦合在一起的对象之一,只需在内部传递上下文(按委托,按接口,按引用)。

答案 1 :(得分:0)

@eocron提出的解决方案可能不那么方便,但仍然:

public interface IWithUserDetails
{
    UserDetails UserDetails();
}

public class ExampleBaseController : Microsoft.AspNetCore.Mvc.Controller, IWithUserDetails
{
    public UserDetails UserDetails()
    {
        return Request.GetUserDetailsFromHttpHeaders();
    }
}

使用相同的类和方法名称并不是最好的方法,但就像带有属性的示例中一样

答案 2 :(得分:0)

这是另一种观点:

public interface IUserDetailsProviderOptions
{
    Func<UserDetails> UserDetailsProvider { get; set; }
}

public class DefaultUserDetailsProviderOptions : IUserDetailsProviderOptions
{
    public Func<UserDetails> UserDetailsProvider {get; set;}
}

public class ExampleBaseController : Microsoft.AspNetCore.Mvc.Controller
{
    private readonly Func<UserDetails> _userDetailsProvider;
    public UserDetails UserDetails => _userDetailsProvider();

    public ExampleBaseController(IUserDetailsProviderOptions options)
    {
         _userDetailsProvider = options.UserDetailsProvider ??
                              Request.GetUserDetailsFromHttpHeaders;
    }
}

像这样在Startup.cs中注册:

services.AddSingleton<IUserDetailsProviderOptions, DefaultUserDetailsProviderOptions>();

在测试中,您可以这样做:

public class StubUserDetailsOption : IUserDetailsProviderOptions
{
    public Func<UserDetails> UserDetailsProvider { get; set; } = () => new StubDetails();
}

var controller = new ExampleBaseController(new StubUserDetailsOption());

并进行测试。