11

I've a method. which retrieve a document from hard disk. I can't test this from unit testing. It always throw an exception invalid null path or something. How to test that. I've temporarily created session for unit test. But I can't for this Server.MapPath. How to do that ?

Jeeva J
  • 2,839
  • 8
  • 33
  • 72
  • You'll need to mock the server object, [This question](http://stackoverflow.com/questions/17332219/mock-server-in-a-controller) talks about it, though this is for MVC you should still be able to apply the same logic. – Liam Oct 24 '13 at 10:17
  • 1
    You can't. Statics are kinda hard to mock. Consider changing your original code which need the value from the Server.MapPath to take a Action delegate which encapsulates the Server.MapPath. Then in your tests you can mock this. – Marvin Smit Oct 24 '13 at 10:17
  • Yeah, like @MarvinSmit says, you'll need to move to a dependency injection pattern to achieve this – Liam Oct 24 '13 at 10:19
  • 3
    Hold on a mo! [You've asked this question before](http://stackoverflow.com/questions/19511228/how-to-do-unit-test-for-httpcontext-current-server-mappath) -1 – Liam Oct 24 '13 at 10:25
  • Thank you all. But I've an question, as per Mock do leave when function call occur and give the result which we mentioned for example, if the control comes to GetUser, Do GetUser always return true ? mock.Setup(foo => foo.GetUser(userId).Returns(true); – Jeeva J Oct 24 '13 at 10:29
  • @Liam, Sorry . But I can't understand the people answer. That's y – Jeeva J Oct 24 '13 at 10:30
  • That depends on what your test is supposed to do. If your test relies on it returning true then yes. Remember your not testing Server.MapPath, your testing **your** code. – Liam Oct 24 '13 at 10:41
  • PS, if you don't understand the answers then ask questions or reference previous answers in your new question if you need extra info. Asking the same question over and over just clutters up SO and doesn't allow people to elaborate on the issues your unsure on. You could simply just get the same answers over and over too. – Liam Oct 24 '13 at 10:42
  • Ok. I'm testing my code. But in that code when control comes to Server.MapPath it always return null and throws error. How to find the solution for this ? – Jeeva J Oct 24 '13 at 10:50
  • 1
    @JeevaJsb, I'm not sure how many times we can say this, I'm going to write it all out one more time, you need to mock Server.MapPath. To mock it you need to change your code as you cannot mock a static method. Krzysztof Cieslak has provided an answer below, you need to do what that answer says there is nothing else to add to this. – Liam Oct 24 '13 at 10:58
  • Too add a little bit more detail because I feel your not grasping the basics here read these questions [what is mocking](http://stackoverflow.com/questions/2665812/what-is-mocking) and [what is dependency injection](http://stackoverflow.com/questions/130794/what-is-dependency-injection) – Liam Oct 24 '13 at 11:02
  • Thank you. would any one help me with the following link http://stackoverflow.com/questions/9624242/setting-the-httpcontext-current-session-in-unit-test Based on this I've created all. Now I should set virtual path and physical path in unit testing . How to do that ? – Jeeva J Oct 24 '13 at 11:51
  • Sorry all. Thank you for your valuable time – Jeeva J Oct 24 '13 at 12:06

3 Answers3

35

You can use dependancy injection and abstraction over Server.MapPath

public interface IPathProvider
{
   string MapPath(string path);
}

And production implementation would be:

public class ServerPathProvider : IPathProvider
{
     public string MapPath(string path)
     {
          return HttpContext.Current.Server.MapPath(path);
     }
}

While testing one:

public class TestPathProvider : IPathProvider
{
    public string MapPath(string path)
    {
        return Path.Combine(@"C:\project\",path);
    }
}
Krzysztof Cieslak
  • 1,595
  • 12
  • 14
  • 10
    *cough* Path.Combine instead of + – Marvin Smit Oct 24 '13 at 10:23
  • What's the benefit of Interface here?. Instead, can we simply make 2 class files and call them accordingly 1 from an actual app and 1 from test endpoint ? Can someone please explain the benefit of this interface here? – Sebastian May 30 '16 at 11:25
  • 1
    Upvoting because this is a really good simple, easy to wrap your head around example of dependency injection. Thanks for that! – Nick DeMayo Jun 13 '16 at 11:02
  • Sorry, I don't see how that helps. In my case, I am unit testing an assembly which in turn accepts"~/" relative paths inside, transforming them into absolute paths via MapPath and I don't want to change the assembly (it works fine inside a web project), all I need to do is provide a valid Http context to be able to use the assembly inside a unit test. You are calling HttpContext.Current.Server.MapPath inside your MapPath belonging to ServerPathProvider - but that won't work in the unit test because HttpContext.Current is not initialized. – Matt Jul 11 '16 at 13:32
  • This is a great and simple understandable sample. – Jeeva J Jan 10 '19 at 09:05
  • any way of not hardcoding it – Sana Apr 30 '19 at 11:47
9

If you need to test legacy code which you can't or don't want to change, you can try FakeHttpContext.

This is how it works:

var expectedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "path");
using (new FakeHttpContext())
{
    var mappedPath = Http.Context.Current.Server.MapPath("path");
    Assert.Equal(expectedPath, mappedPath);
}
vAD
  • 231
  • 3
  • 6
  • How can the base MapPath be *set* with FakeHttpContext? – djk Apr 04 '17 at 15:07
  • @Hannobo, currently BasePath is `AppDomain.CurrentDomain.BaseDirectory`. If you need to change it please create an issue [here](https://github.com/vadimzozulya/FakeHttpContext/issues) – vAD Apr 06 '17 at 10:50
  • You may use this HttpContext.Current.Server.MapPath("myPath").Should().Be(expectedPath); wtih FakeHttpContext – PedroSouki Aug 30 '18 at 20:18
0

I was using NSubstitute and I implemented it as below:

 var fakeContext = Substitute.For<HttpContextBase>();
fakeContext.Server.MapPath(Arg.Any<string>()).ReturnsForAnyArgs("/set-path/");