根据 HttpRequest 模拟 属性

Mocking property depending on HttpRequest

我有以下设置:

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 的成员,我不知道如何实现。

如果你想模拟某物,你应该首先允许模拟。如果你想模拟 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 模拟为 return 一些 "foobar"。在生产中,您只需调用 Request.

内部传递的 GetUserDetailsFromHttpHeaders() 方法

回答有关请求和控制器关系的问题。 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
    }
}

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

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

public interface IWithUserDetails
{
    UserDetails UserDetails();
}

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

class 和方法的同名不是最好的方法,但它就像示例中的 属性

这里换一个观点:

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

并进行测试。