Unit test controller that uses application scoped

2019-03-01 03:09发布

I'm building an ASP.NET MVC4 app. I'm not using any mocking framework and, if possible, would prefer not to at this point. My question is 2 parts.

I have a controller that uses a variable created in Global.asax. In the controller I access the variable like this.

HttpContext.Application["MyVar"]

1) Is this a best-practice for application wide variable usage? If not, what's the best way?

In an attempt to unit test this controller I added the following code (from here) to my test method.

MyController target = new MyController();
var request = new HttpRequest("", "http://example.com/", "");
var response = new HttpResponse(System.IO.TextWriter.Null);
var httpContext = new HttpContextWrapper(new HttpContext(request, response));
target.ControllerContext = new ControllerContext(httpContext, new RouteData(), target);
target.ControllerContext.HttpContext.Application["MyVar"] = new MyVar();

The problem is I can't add anything to Application. The last line of code doesn't seem to do anything and the collection remains empty. I've also tried this in VS's Immediate Window without success.

2) In the unit test, how can I add the application level variables the controller needs?

2条回答
【Aperson】
2楼-- · 2019-03-01 03:48

Is this a best-practice for application wide variable usage?

Best practice is a bit of a subjective notion and without fully explaining your scenario and what precisely are you trying to achieve I prefer not to discuss it.

We cannot discuss whether this is best practice but from what I can see it is not wrong either. It is not wrong because you are using the abstractions allowing the code to be unit tested.

In the unit test, how can I add the application level variables the controller needs?

You could use a mocking framework such as Rhino Mocks to mock the abstractions that the controller needs. Let's take as an example the following controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var myVar = (MyVar)HttpContext.Application["MyVar"];
        return Content(myVar.Foo);
    }
}

and we would like to unit test the Index action. Here's a sample unit test:

[TestMethod]
public void Index_Action_Should_Retrieve_MyVal_From_AppState()
{
    // arrange
    var target = new HomeController();
    var httpContext = MockRepository.GeneratePartialMock<HttpContextBase>();
    var application = MockRepository.GeneratePartialMock<HttpApplicationStateBase>();
    application.Expect(x => x["MyVar"]).Return(new MyVar { Foo = "bar" });
    httpContext.Expect(x => x.Application).Return(application);
    target.ControllerContext = new ControllerContext(httpContext, new RouteData(), target);

    // act
    var actual = target.Index() as ContentResult;

    // assert
    Assert.AreEqual("bar", actual.Content);
}
查看更多
何必那么认真
3楼-- · 2019-03-01 04:12

In general globals aren't good for testing. There are at least two approaches you could take.

  1. Use a mocking framework like Pex/Moles, NMock, etc.

  2. Use an inversion-of-control approach (NInject is my favorite). If class like a controller has an external dependency, it asks for the interface, typically in its constructor.

    private readonly IApplicationSettings _settings;

    public MyController(IApplicationSettings settings) { _settings = settings; }

    void someMethod() { _settings.Get("MyVar"); }

This way you can write real and test implementations.

public LiveAppSettings : IApplicationSettings
{
    public string Get(string key)
    { 
        return HttpContext.Current.Application[key];
    }
}

With Ninject, you can bind either implementation at application startup:

var kernel = new StandardKernel();
kernel.Bind<IApplicationSettings>().To<LiveAppSettings>();
查看更多
登录 后发表回答