单元测试控制器 - 如何处理连接字符串?

Unit Testing a Controller - How Do I Handle the Connection String?

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

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

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

这是数据库 class 中我的 GetConnectionString() 方法,它通过我的 SendService 构造函数发送。

    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。

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

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

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 class 并在 ConfigureServices 方法中,您可以将接口映射到具体实现。

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

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

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

和一个使用您的特定数据访问的实现 code/ORM

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

现在您的服务 class 将是

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

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

例如,如果您使用的是 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();