65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Unity 依赖注入模拟 MVC 控制器中的外部 WCF 服务

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2014年7月18日

CPOL

4分钟阅读

viewsIcon

21386

downloadIcon

283

在 MVC 控制器中模拟(moq)您的服务。

引言

下载WcfUnity.zip (VS2013)

为了便于打包,我已从zip下载中删除了所有NuGet包和bin dll。但是,当您编译项目时,解决方案将从NuGet下载所有相应的程序集。我使用的是Visual Studio 2013,但您可以使用VS2012 Web Express及更高版本。

背景

在单元测试控制器方法时,您需要模拟代码与之通信的层。这可以使用RhinoMock或MOQ等模拟框架轻松完成。第一层和最后一层永远无法模拟,因为您无法控制UI(第一层)或外部服务或数据库过程(最后一层)。对于这些场景,您将使用Selenium或CodedUI等集成测试工具来执行端到端测试(从UI到服务\数据库)。

但是,在某些情况下,您需要模拟倒数第二层,但您对其的控制仍然有限,因为它可能部署在服务器上,而您的应用程序正在使用它——例如,一个与外部数据库通信或执行某些数字计算逻辑的SOAP服务。在单元测试中,您只关心控制器方法中的逻辑,而不是服务——因此,您希望模拟服务调用,因为服务在部署之前已经过单元测试。

IOC & 项目结构

对于我的IOC容器,我将使用MVC.Unity 4 Nuget包。我的测试项目分为三个项目:

  1. WCF服务项目
  2. MVC Web应用程序
  3. 测试项目

代码解释

为了简单起见,我在Global.asax类中的Application_Start方法中设置了Unity解析器(更好的方法是创建一个静态的BootStraper方法并从Application_Start调用它——使代码更易于维护——因为在普通应用程序中,这个方法最终会变得代码拥挤)。

在添加Unity解析器代码之前,您需要将外部服务引入您的项目。在我的例子中,我将StockServices部署到我的本地IIS,然后像往常一样将WSDL引入我的项目。

protected void Application_Start()
        {           
            var container = new UnityContainer();
            container
                .RegisterType<IStock, StockClient>()
                .Configure<InjectedMembers>()
                .ConfigureInjectionFor<StockClient>(new InjectionConstructor("*"));
            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        }

 

上面代码片段中,将接口解析为具体类的代码非常标准。唯一有趣的是,我从哪里获取服务名称——StockCleint

如果您在项目中双击服务引用,将打开**对象浏览器**窗口,您可以向下滚动到服务条目——服务名称将始终带有Client后缀。这就是您希望服务接口解析到的具体类名。

现在您知道在哪里以及如何解析服务依赖项。接下来的部分是如何将服务注入我的控制器,然后在*构造函数*注入后执行服务方法。

 

Controller 代码

在下面的代码片段中,有几行代码值得我们关注。 namely,我们接口IStock的构造函数注入和私有变量stock 只有从MVC 3开始,才有可能创建一个带有构造函数的控制器类。因此,我们可以使用Unity的构造函数依赖注入方法,通过构造函数实现我们的具体类。

现在将服务注入控制器非常容易,调用服务方法也直观——但需要注意的重要一点是,您正在注入您的接口,因此稍后可以在测试中模拟您的接口。为了简单起见,我只是调用服务来返回一个数据类型和一个类结构。

 public class HomeController : Controller
    {
        private readonly StockService.IStock stock;

        public HomeController(StockService.IStock stk)
        {
            stock = stk;
        }

        public ActionResult Index()
        {
            ViewBag.Message = stock.GetData(10);
            
            return View("Index");
        }

        public ActionResult About()
        {            
            StockService.StockData data = stock.GetStockData();
            ViewBag.Message = String.Format("Your stock {0} has a value {1}.", data.StockName, data.StockValue);

            return View("About", data);
        }        
    }

创建测试

最困难的部分已经完成,因为我们只需像平常一样创建单元测试,模拟服务及其方法。我们将模拟服务注入我们正在测试的控制器构造函数,然后像平常一样测试控制器方法。在下面的测试中,我只是测试了模拟的模型或ViewBag,但您可能希望使用MVC助手来测试生成的HTML等。

[TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void Index()
        {
            // test variables
            string expectedViewName = "Index";
            string returnValue = "300";

            // mocking objects
            Mock<IStock> mockIStock = new Mock<IStock>();
            mockIStock.Setup(t => t.GetData(It.IsAny<int>())).Returns(returnValue);

            // Arrange
            HomeController controller = new HomeController(mockIStock.Object);

            // Act
            ViewResult result = controller.Index() as ViewResult;

            // Assert
            Assert.IsNotNull(result);
            Assert.AreEqual(result.ViewBag.message, returnValue);
            Assert.AreEqual(expectedViewName, result.ViewName, "View name should have been {0}", expectedViewName);
        }

        [TestMethod]
        public void About()
        {
            // test variables
            string expectedViewName = "About";
            string stockName = "BON";
            double stockValue = 1;

            // mocking objects
            Mock<StockData> mockStockData = new Mock<StockData>();
            mockStockData.Object.StockName = stockName;
            mockStockData.Object.StockValue = stockValue; // penny stock :)

            Mock<IStock> mockIStock = new Mock<IStock>();
            mockIStock.Setup(t => t.GetStockData()).Returns(mockStockData.Object);

            // Arrange
            HomeController controller = new HomeController(mockIStock.Object);

            // Act
            ViewResult result = controller.About() as ViewResult;

            // Assert            
            Assert.IsNotNull(result);
            Assert.IsTrue(result.ViewBag.message == String.Format("Your stock {0} has a value {1}.", stockName, stockValue));
            Assert.AreEqual(expectedViewName, result.ViewName, "View name should have been {0}", expectedViewName);
            Assert.AreEqual(result.Model, mockStockData.Object, "Model should have been {0} and {1}", mockStockData.Object.StockName, mockStockData.Object.StockValue);
        }
    }

运行测试

要在VS2012\13中运行测试,只需右键单击测试类并从上下文菜单中选择**运行测试**,这将显示您的测试结果——您可以插入断点并像往常一样调试您的测试。

 

结论

模拟外部服务非常容易,在处理 动态构建的服务终结点时,需要多一个步骤——但同样,在惰性加载服务时,您只需使用终结点详细信息(例如,WSHttpBinding_IStock)作为您的解析具体类。

© . All rights reserved.