单元测试使用windows身份验证的控制器

本文关键字:控制器 身份验证 windows 单元测试 | 更新日期: 2023-09-27 18:28:34

-------请参阅下面的更新,因为我现在已经为依赖注入和MOQ mocking框架的使用设置好了。我仍然想拆分我的存储库,这样它就不会直接依赖于在同一函数中提取windowsUser


我在一个内联网站点中有一个Web API,它填充了一个下拉列表。下拉列表后面的查询将windows用户名作为返回列表的参数。

我意识到我没有正确设置所有这些,因为我无法对其进行单元测试。我需要知道如何设置这些"应该"来允许单元测试,然后单元测试应该是什么样子。

附加信息:这是一个ASP.NET MVC 5应用程序。

接口

public interface ITestRepository
{
    HttpResponseMessage DropDownList();
}

库存

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;            
    }
}

控制器

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

更新1

我已经根据BartoszKP的建议更新了我的控制器,以显示依赖注入。

更新的控制器

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

更新2

我决定使用MOQ作为单元测试的模拟框架。我能够测试一些简单的东西,比如下面的这将测试一个简单的方法,该方法不需要任何参数,也不包括windowsUser部分

[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);
}

我需要帮助测试DropDownList方法(其中包含获取windowsUser的代码)。我需要关于如何打破这种方法的建议。我知道这两部分不应该采用相同的方法。我不知道如何安排拆分windowsUser变量。我意识到这确实应该作为一个参数引入,但我不知道如何引入。

单元测试使用windows身份验证的控制器

您通常不会对存储库进行单元测试(集成测试验证它们是否真的正确地将数据保存在数据库中)-例如,请参阅MSDN上的这篇文章:

通常,很难对存储库本身进行单元测试,因此通常最好为它们编写集成测试。

因此,让我们集中精力只测试控制器。

将控制器更改为将其构造函数中的IExampleRepository作为参数:

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

然后,在单元测试中,使用一个mocking框架(例如RhinoMock)创建一个存根,仅用于测试控制器。

[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));
    }  
}

这只是一个快速&脏示例-CreateRepositoryStub方法当然应该提取到某个测试实用程序类中。也许它应该返回一个流畅的界面,使测试的Arrange部分对给定内容更具可读性。更像:

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

(当然,要有更好的名称来反映您的业务逻辑)。


在ASP.NET MVC的情况下,框架需要知道如何构造控制器。幸运的是,ASP.NET支持依赖注入范式,并且在使用MVC统一时不需要无参数构造函数。


此外,请注意Richard Szalay的评论:

你不应该在WebApi中使用HttpContext.Current——你可以使用来自HttpRequestBase.Userbase.User,它是可模拟的。如果您真的想继续使用HttpContext.Current,请查看Test Init Method 中的Mock HttpContext.Current

当旧代码访问一些全局静态或其他我无法简单参数化的混乱内容时,我发现一个非常有用的技巧是将对资源的访问封装在虚拟方法调用中。然后,您可以将测试中的系统子类化,并在单元测试中使用它。

示例,在System.Random类中使用硬依赖项

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

现在我们更换var rng = new Random();

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

现在我们可以创建该类的可测试版本:

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;
    }
}

这种方法的缺点是,你不能(轻松地)使用(大多数)隔离框架来实现受保护的方法,这是有充分理由的,因为受保护方法是内部的,对你的单元测试来说不应该那么重要。这仍然是一种非常方便的方法,可以用测试来覆盖内容,这样你就可以重构它们,而不必花10个小时不进行测试,在达到"安全"之前尝试对代码进行重大的架构更改。

只是另一个需要记住的工具,我发现它不时派上用场!

编辑:更具体地说,在您的情况下,您可能想要创建一个protected virtual string GetLoggedInUserName()。从技术上讲,这将保持对HttpContext.Current.User.Identity.Name的实际调用未经测试,但您将把它隔离到最简单、尽可能小的方法中,这样您就可以测试代码是否使用正确的args调用了正确的方法,然后您只需要知道HttpContext.Current.User.Identity.Name包含了您想要的内容。稍后可以将其重构为某种用户管理器或登录用户提供程序,您将看到什么最适合您。