如何使用Moq模拟HttpContext.Current.Server.MapPath?

14

我正在对我的主页控制器进行单元测试。这个测试在我添加了一个保存图片的新功能之前是有效的。

引起问题的方法如下所示。

    public static void SaveStarCarCAPImage(int capID)
    {
        byte[] capBinary = Motorpoint2011Data.RetrieveCapImageData(capID);

        if (capBinary != null)
        {
            MemoryStream ioStream = new MemoryStream();
            ioStream = new MemoryStream(capBinary);

            // save the memory stream as an image
            // Read in the data but do not close, before using the stream.

            using (Stream originalBinaryDataStream = ioStream)
            {
                var path = HttpContext.Current.Server.MapPath("/StarVehiclesImages");
                path = System.IO.Path.Combine(path, capID + ".jpg");
                Image image = Image.FromStream(originalBinaryDataStream);
                Image resize = image.GetThumbnailImage(500, 375, null, new IntPtr());
                resize.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
            }
        }
    }

由于调用来自单元测试,HttpContext.Current为空并抛出异常。阅读有关Moq和使用Moq与sessions的教程后,我确信可以完成。

到目前为止,这是我编写的单元测试代码,但问题在于HTTPContext.Current始终为null,并仍然会引发异常。

    protected ControllerContext CreateStubControllerContext(Controller controller)
    {
        var httpContextStub = new Mock<HttpContextBase>
        {
            DefaultValue = DefaultValue.Mock
        };

        return new ControllerContext(httpContextStub.Object, new RouteData(), controller);
    }

    [TestMethod]
    public void Index()
    {
        // Arrange
        HomeController controller = new HomeController();            
        controller.SetFakeControllerContext();

        var context = controller.HttpContext;

        Mock.Get(context).Setup(s => s.Server.MapPath("/StarVehiclesImages")).Returns("My Path");

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

        // Assert
        HomePageModel model = (HomePageModel)result.Model;
        Assert.AreEqual("Welcome to ASP.NET MVC!", model.Message);
        Assert.AreEqual(typeof(List<Vehicle>), model.VehicleMakes.GetType());
        Assert.IsTrue(model.VehicleMakes.Exists(x => x.Make.Trim().Equals("Ford", StringComparison.OrdinalIgnoreCase)));
    }
4个回答

13

HttpContext.Current是一个你在期望进行单元测试的代码中绝不应该使用的东西。它是一个静态方法,如果没有Web上下文(在单元测试情况下通常是这种情况),则仅返回null,并且无法模拟。因此,重构代码的一种方式如下:

public static void SaveStarCarCAPImage(int capID, string path)
{
    byte[] capBinary = Motorpoint2011Data.RetrieveCapImageData(capID, path);

    if (capBinary != null)
    {
        MemoryStream ioStream = new MemoryStream();
        ioStream = new MemoryStream(capBinary);

        // save the memory stream as an image
        // Read in the data but do not close, before using the stream.

        using (Stream originalBinaryDataStream = ioStream)
        {
            path = System.IO.Path.Combine(path, capID + ".jpg");
            Image image = Image.FromStream(originalBinaryDataStream);
            Image resize = image.GetThumbnailImage(500, 375, null, new IntPtr());
            resize.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
        }
    }
}

你看,现在这个方法不再依赖于任何网络上下文,可以被孤立地测试。调用者将负责传递正确的路径。


好的建议,但是Moq似乎有使用“Mock.Get(context).Setup()”来模拟HttpContext的选项,这个方法不可行吗?或者在测试方法中完全避免使用HttpContext是最佳实践吗? - JGilmartin
1
@Truegilly,避免使用HttpContext.Current。这是一个好的实践。 - Darin Dimitrov

10

我同意Darin的回答,但如果你真的需要模拟Server.MapPath函数,你可以像这样做。

//...
var serverMock = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
serverMock.Setup(i => i.MapPath(It.IsAny<String>()))
   .Returns((String a) => a.Replace("~/", @"C:\testserverdir\").Replace("/",@"\"));
//...

执行此操作,模拟程序将仅使用 c:\testserverdir\ 函数替换 ~/ 符号。

希望能有所帮助!


5
我有疑问,因为无法模拟HttpContext的某个部分,因为context.Server没有setter。所以我看不到任何使用您提供的HttpServerUtilityBase模拟与HttpContext实际配合的方法。 - buddybubble

3

有时候,仅模拟对 server.MapPath 的调用会很方便。 使用 moq,这个解决方案对我有效。 我只模拟了应用程序的基本路径。

        _contextMock = new Mock<HttpContextBase>();
        _contextMock.Setup(x => x.Server.MapPath("~")).Returns(@"c:\yourPath\App");
        _controller = new YourController();
        _controller.ControllerContext = new ControllerContext(_contextMock.Object, new RouteData(), _controller);

在您的控制器中,现在可以使用 Server.MapPath("~")。


1
以下对我有效。
string pathToTestScripts = @"..\..\..\RelatavePathToTestScripts\";
string testScriptsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, pathToTestScripts);

var server = new Mock<HttpServerUtilityBase>(); // Need to mock Server.MapPath() and give location of random.ps1
server.Setup(x => x.MapPath(PowershellScripts.RANDOM_PATH)).Returns(testScriptsFolder + "random.ps1");

var request = new Mock<HttpRequestBase>(); // To mock a query param of s=1 (which will make random.ps1 run for 1 second)
request.Setup(x => x.QueryString).Returns(new System.Collections.Specialized.NameValueCollection { { "s", "1" } });

var httpContext = new Mock<HttpContextBase>();
httpContext.Setup(x => x.Server).Returns(server.Object);
httpContext.Setup(x => x.Request).Returns(request.Object);

YourController controller = new YourController();
controller.ControllerContext = new ControllerContext(httpContext.Object, new RouteData(), controller);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接