【问题标题】:Unit Testing a Controller - How Do I Handle the Connection String?对控制器进行单元测试 - 如何处理连接字符串?
【发布时间】:2016-04-04 18:17:10
【问题描述】:

我可以让它发挥作用,但我想知道最佳做法是什么以及为什么。我有一个控制器、一个模型和一个存储库,现在我想对控制器进行单元测试。我只是在写一个简单的测试来确保返回正确的视图。

这是我在控制器中的方法:

    public ActionResult Selections(SelectionsViewModel model)
    {
        for (int i = 0; i < model.Sends.Count; i++)
        {
            Send send = new Send(new SendService(new Database().GetConnectionString()))
            {
                SendID = model.Sends[i].SendID,
                Title = model.Sends[i].Title,
                Subject = model.Sends[i].Subject,
                SentDate = model.Sends[i].SentDate,
                TimesViewed = model.Sends[i].TimesViewed,
                Include = model.Sends[i].Include,
                Exclude = model.Sends[i].Exclude
            };
            send.UpdateIncludeExclude();
        }

        return View(model);
    }

这是通过我的 SendService 构造函数发送的 Database 类中的 GetConnectionString() 方法。

    public string GetConnectionString()
    {
        return System.Configuration.ConfigurationManager.ConnectionStrings["DEVConnectionString"].ToString();
    }

最后,我的单元测试:

    [Test]
    public void TestAssignmentSelections()
    {
        var obj = new AssignmentController();

        var actResult = obj.Selections() as ViewResult;

        NUnit.Framework.Assert.That(actResult.ViewName, Is.EqualTo("Selections"));
    }

现在,我的单元测试失败了,我明白了原因。我的单元测试项目无法访问我正在测试的连接字符串所在的项目的 web.config。

我已经进行了一些研究,显然只是将 web.config 添加到我的单元测试项目中并将连接字符串也放入其中即可使其正常工作.. 但这似乎是一个 hack。

解决此问题的最佳方法是什么?有没有另一种方法来编写我的代码来适应这个?

【问题讨论】:

    标签: asp.net-mvc unit-testing web-config


    【解决方案1】:

    您想让您的控制器单元可测试吗?不要这样做。

    new SendService(
    

    使用此代码,您可以对具体的服务实现和数据访问代码实现进行硬编码。在您的单元测试中,您不应该真正访问数据库中的数据。相反,您应该提供一个模拟数据访问实现。

    接口来了,你需要为你的SendService创建一个接口。

    public interface ISendService
    { 
      void SomeMethod();
    }
    

    现在您的SendService 将成为此接口的具体实现

    public class SendService : ISendService
    {
       public void SomeMethod()
       {
         // Do something
       }
    }
    

    现在更新您的控制器,使其具有一个构造函数,我们将在其中注入 ISendService 的实现。

    public class YourController : Controller
    {
       private ISendService sendService;
       public YourController(ISendService sendService)
       {
         this.sendService = sendService;
       }      
       public ActionResult YourActionMethod()
       {
        // use this.sendService.SomeMethod();
       }
    }
    

    并且你可以使用一些依赖注入框架来告诉 MVC 框架在代码运行时使用哪个接口实现。如果您使用的是 MVC6,它有一个可以使用的内置依赖注入提供程序。因此,转到您的 Startup 类并在您的 ConfigureServices 方法中,您可以将接口映射到具体实现。

    public class Startup
    {
      public void ConfigureServices(IServiceCollection services)
      {
         services.AddTransient<ISendService, SendService>();
      }
    }
    

    如果您使用的是先前版本的 MVC,您可以考虑使用 Unity、Ninject 等 DI 框架。您可以稍后为您的数据访问/服务层执行相同的方法。即:为数据访问创建一个接口并将其注入到您的 SendService。

    public Interface IDataAccess
    {
      string GetName(int id);
    }
    

    以及使用您的特定数据访问代码/ORM 的实现

    public class EFDataAccess : IDataAccess
    {
      public string GetName(int id)
      {
         // return a string from db using EF
      }
    }
    

    所以现在你的服务类将是

    public class SendService : ISendService
    {
      private IDataAccess dataAccess;
      public SendService(IDataAccess dataAccess)
      {
        this.dataAccess=dataAccess;
      } 
      // to do : Implement methods of your ISendService interface. 
      // you may use this.dataAccess in those methods as needed.
    }
    

    在您的单元测试中,您可以创建接口的模拟实现,它返回静态数据而不是访问数据库。

    例如,如果您使用的是 Moq 模拟框架,则可以这样做。

    var m = new Mock<IDataAccess>();
    var m.Setup(s=>s.GetName(It.IsAny<int>())).Returns("Test");
    var s = new SendService(m);
    var result= s.SomeMethod();
    

    【讨论】:

    • 这很有帮助,我想我需要做出一些改变。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2012-04-24
    • 2018-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-31
    相关资源
    最近更新 更多