简体   繁体   中英

Unit Testing a controller that uses windows authentication

------- Please see updates below as I now have this set up for dependency injection and the use of the MOQ mocking framework. I'd still like to split up my repository so it doesn't directly depend on pulling the windowsUser within the same function.


I have a Web API in an intranet site that populates a dropdown. The query behind the dropdown takes the windows username as a parameter to return the list.

I realize I don't have all of this set up correctly because I'm not able to unit test it. I need to know how this "should" be set up to allow unit testing and then what the unit tests should look like.

Additional info: this is an ASP.NET MVC 5 application.

INTERFACE

public interface ITestRepository
{
    HttpResponseMessage DropDownList();
}

REPOSITORY

public class ExampleRepository : IExampleRepository
{
    //Accessing the data through Entity Framework
    private MyDatabaseEntities db = new MyDatabaseEntities();

    public HttpResponseMessage DropDownList()
    {
        //Get the current windows user
        string windowsUser =  HttpContext.Current.User.Identity.Name;

        //Pass the parameter to a procedure running a select query
        var sourceQuery = (from p in db.spDropDownList(windowsUser)
                           select p).ToList();

        string result = JsonConvert.SerializeObject(sourceQuery);
        var response = new HttpResponseMessage();
        response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");

        return response;            
    }
}

CONTROLLER

public class ExampleController : ApiController
{
    private IExampleRepository _exampleRepository;

    public ExampleController()
    {
        _exampleRepository = new ExampleRepository();
    }

    [HttpGet]
    public HttpResponseMessage DropDownList()
    {
        try
        {
            return _exampleRepository.DropDownList();
        }
        catch
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
    }
}

UPDATE 1

I have updated my Controller based on BartoszKP's suggestion to show dependency injection.

UPDATED CONTROLLER

public class ExampleController : ApiController
{
    private IExampleRepository _exampleRepository;

    //Dependency Injection
    public ExampleController(IExampleRepository exampleRepository)
    {
        _exampleRepository = exampleRepository;
    }

    [HttpGet]
    public HttpResponseMessage DropDownList()
    {
        try
        {
            return _exampleRepository.DropDownList();
        }
        catch
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
    }
}

UPDATE 2

I have decided to use MOQ as a mocking framework for unit testing. I'm able to test something simple, like the following. This would test a simple method that doesn't take any parameters and doesn't include the windowsUser part .

[TestMethod]
public void ExampleOfAnotherTest()
{
    //Arrange
    var mockRepository = new Mock<IExampleRepository>();
    mockRepository
        .Setup(x => x.DropDownList())
        .Returns(new HttpResponseMessage(HttpStatusCode.OK));

    ExampleController controller = new ExampleController(mockRepository.Object);
    controller.Request = new HttpRequestMessage();
    controller.Configuration = new HttpConfiguration();

    //Act            
    var response = controller.DropDownList();

    //Assert
    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}

I need help testing the DropDownList method (one that does include code to get the windowsUser) . I need advice on how to break this method apart. I know both parts shouldn't been in the same method. I don't know how to arrange splitting out the windowsUser variable. I realize this really should be brought in as a parameter, but I can't figure out how.

You usually do not unit-test repositories (integration tests verify if they really persist the data in the database correctly) - see for example this article on MSDN :

Typically, it is difficult to unit test the repositories themselves, so it is often better to write integration tests for them.

So, let's focus on testing only the controller.

Change the controller to take IExampleRepository in its constructor as a parameter:

private IExampleRepository _exampleRepository;

public ExampleController(IExampleRepository exampleRepository)
{
    _exampleRepository = exampleRepository;
}

Then, in your unit tests, use one of mocking frameworks (such as RhinoMock for example) to create a stub for the sole purpose of testing the controller.

[TestFixture]
public class ExampleTestFixture
{
    private IExampleRepository CreateRepositoryStub(fake data)
    {
        var exampleRepositoryStub = ...; // create the stub with a mocking framework

        // make the stub return given fake data

        return exampleRepositoryStub;
    }

    [Test]
    public void GivenX_WhenDropDownListIsRequested_ReturnsY()
    {
        // Arrange
        var exampleRepositoryStub = CreateRepositoryStub(X);
        var exampleController = new ExampleController(exampleRepositoryStub);

        // Act
        var result = exampleController.DropDownList();

        // Assert
        Assert.That(result, Is.Equal(Y));
    }  
}

This is just a quick&dirty example - CreateRepositoryStub method should be of course extracted to some test utility class. Perhaps it should return a fluent interface to make the test's Arrange section more readable on what is given. Something more like:

// Arrange
var exampleController
    = GivenAController()
      .WithFakeData(X);

(with better names that reflect your business logic of course).


In case of ASP.NET MVC, the framework needs to know how to construct the controller. Fortunately, ASP.NET supports the Dependency Injection paradigm and a parameterless constructor is not required when using MVC unity .


Also, note the comment by Richard Szalay :

You shouldn't use HttpContext.Current in WebApi - you can use base.User which comes from HttpRequestBase.User and is mockable. If you really want to continue using HttpContext.Current , take a look at Mock HttpContext.Current in Test Init Method

One trick that I find very useful when trying to make old code testable when said code is accessing some global static or other messy stuff that I can't easily just parameterize is to wrap access to the resource in a virtual method call. Then you can subclass your system under test and use that in the unit test instead.

Example, using a hard dependency in the System.Random class

public class Untestable
{
    public int CalculateSomethingRandom()
    {
        return new Random().Next() + new Random().Next();
    }
}

Now we replace var rng = new Random();

public class Untestable
{
    public int CalculateSomethingRandom()
    {
        return GetRandomNumber() + GetRandomNumber();
    }

    protected virtual int GetRandomNumber()
    {
        return new Random().Next();
    }
}

Now we can create a testable version of the class:

public class Testable : Untestable
{
    protected override int GetRandomNumber()
    {
        // You can return whatever you want for your test here,
        // it depends on what type of behaviour you are faking.
        // You can easily inject values here via a constructor or
        // some public field in the subclass. You can also add
        // counters for times method was called, save the args etc.
        return 4;
    }
}

The drawback with this method is that you can't use (most) isolation frameworks to implement protected methods (easily), and for good reason, since protected methods are sort of internal and shouldn't be all that important to your unit tests. It's still a really handy way of getting things covered with tests so you can refactor them, instead of having to spend 10 hours without tests, trying to do major architectual changes to your code before you get to "safety".

Just another tool to keep in mind, I find it comes in handy from time to time!

EDIT: More concretely, in your case you might want to create a protected virtual string GetLoggedInUserName() . This will technically speaking keep the actual call to HttpContext.Current.User.Identity.Name untested, but you will have isolated it to the simplest smallest possible method, so you can test that the code is calling the correct method the right amount of times with the correct args, and then you simply have to know that HttpContext.Current.User.Identity.Name contains what you want. This can later be refactored into some sort of user manager or logged in user provider, you'll see what suits best as you go along.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM