Mocking property depending on HttpRequest

by Răzvan Flavius Panda   Last Updated December 06, 2018 15:26 PM

I'm having the following setup:

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

I need to be able to inject UserDetails during production run and also be able to mock it during tests. Since UserDetails depends on Request and Request is a member of Microsoft.AspNetCore.Mvc.Controller I do not know how to achieve this.



Answers 2


If you want to mock something, you should first allow mocking on it. If you want to mock UserDetails you should allow mocking on its getter and pass required context inside newly crafted contract:

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

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

So, in test you mock IUserDetailsProvider to return some "foobar". In production you just invoking GetUserDetailsFromHttpHeaders() method on passed inside Request.

To answer question about Request and Controller relations. Controller depends on Request, yes, and microsoft thought that is will be good to strongly merge them together instead of passing dependency, for example like this:

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

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

Or even like this:

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

They instead used Property injection, which leaves developer with no way to affect injection. This is a problem. But not unsolvable - you just pass context inside, if you need one of those coupled together objects.

eocron
eocron
December 06, 2018 15:02 PM

It could be not so convinient as solution, proposed by @eocron, but still:

public interface IWithUserDetails
{
    UserDetails UserDetails();
}

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

Same name for class and method is not a best way to do, but it was like it in the example with a property

Belurd
Belurd
December 06, 2018 15:11 PM

Related Questions


registering DbContext with multiple parameters

Updated July 01, 2018 20:26 PM

Inversion of Control vs Dependency Injection

Updated March 16, 2017 15:26 PM


How to inject object builders into MVC controller?

Updated August 07, 2018 15:26 PM