根据 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());
并进行测试。
我有以下设置:
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());
并进行测试。