Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mock HttpContext.Current.Server.MapPath using Moq?

im unit testing my home controller. This test worked fine until I added a new feature which saves images.

The method that’s causing the issue is this below.

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

As the call is coming from a unit test, HttpContext.Current is null and throws an exception. After reading about Moq and some of the tutorials about using Moq with sessions, im sure it can be done.

so far this the unit test code have come up with, but the issue is HTTPContext.Current is always null, and still throws the exception.

    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)));
    }
like image 435
JGilmartin Avatar asked Mar 21 '11 12:03

JGilmartin


4 Answers

HttpContext.Current is something that you should absolutely never use if you ever expect your code to be unit tested. It is a static method which simply returns null if there is no web context which is the case of a unit test and cannot be mocked. So one way to refactor your code would be the following:

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

You see, now this method no longer depends on any web context and can be tested in isolation. It will be the responsibility of the caller to pass the correct path.

like image 124
Darin Dimitrov Avatar answered Nov 09 '22 03:11

Darin Dimitrov


It is sometimes handy to just mock the call to server.MapPath. This solution works for me using moq. I only mock the base path to the application.

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

In your controller you can now user Server.MapPath("~").

like image 34
Karl Avatar answered Nov 09 '22 04:11

Karl


I agree with the Darin´s answer but if you really need to moq the Server.MapPath function you could do something like this

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

Performing this, the mock will simply replace the ~/ with the c:\testserverdir\ function

Hope it helps!

like image 10
Lelis718 Avatar answered Nov 09 '22 05:11

Lelis718


Below works for me.

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);
like image 1
sky Avatar answered Nov 09 '22 05:11

sky