views:

147

answers:

5

I am testing a static method that builds a URL string after checking proxies and hostnames and all kinds of things. This method internally relies on the static flag System.Net.Sockets.Socket.OSSupportsIPv6. Because it's static, I cannot mock this dependency.

EDIT: (This is simplified down a lot... I can't modify the structure of the method with the flag to accept a true/false).

On XP development machines, the method under question returns a predictable string result (in this case, http://hostname/.... ). Our build server, which support IPv6, returns a totally different result (it gives me something like http://192.168.0.1:80/....). Please don't ask why - the point is that there are two different output types that vary on an operating system dependency.

The test needs to validate that the returned hostname or IP address is valid. The outputs are easy to check. The problem is that I can only get one of the possible outputs, depending on which machine the test is run on.

What's the best practice for writing the test in this case? Do I put an if statement in my test which checks the flag and then looks for the two different outputs? This seems sketchy to me because

  1. the test is behaving differently depending on the environment it's run in

  2. I'm basically coupling the test to the method, because you need to know the internals of the method to set up the two cases.

Do I set up two tests, one for each environment, that simply pass if they're in the wrong environment? Do I write some complex regular expression that can separate out what kind of result it got and use more if/else logic to verify it?

Any suggestions would help!

A: 

I don't understand why you can't mock this. Your method should take a bool SupportsIPv6 parameter, and in the real code, you pass it System.Net.Sockets.Socket.OSSupportsIPv6. Then you test your method with your parameter being both true and false, and make sure it spits out the right formats for both true and false cases. Voila, done.

mquander
Well, that then makes the call sites hard to test... it passes the buck, basically. That *may* not be a problem, admittedly...
Jon Skeet
That would definitely be ideal. I actually simplified it down a lot, there's a couple of external calls and the flag it relies on is actually in a .dll I don't have control over.
womp
I see. Guess that's a shame, then.
mquander
A: 

It does happen sometimes that it's too hard to make a useful unit test. I'm not confident this is one of those cases, but it's worth remembering.

That said, now, define what "valid" means? You can certainly test of it's well-formed: [0-9]{1,3} is a start on a regex for that.

You can test that it's reachable with an ICMP echo packet (or a ping(8)).

If you can define what "valid" means to you, that should defione a unit test.

Charlie Martin
That's a good point to be sure. In this case the question is more about the structure of the test - I definitely know when the outputs are valid. But depending on where you run the test, you can only test one of the outputs.
womp
+1  A: 

Can you hide the static method behind an abstract interface?

interface ISupportIPv6
{
  bool supported { get; }
}

//class for unit testing
class TestSupportsIPv6 : ISupportIPv6
{
  public bool supported { get { return true; } }
}

//another class for more unit testing
class TestDoesNotSupportIPv6 : ISupportIPv6
{
  public bool supported { get { return false; } }
}

//class for real life (not unit testing)
class OsSupportsIPv6 : ISupportIPv6
{
  public bool supported { get {
    return System.Net.Sockets.Socket.OSSupportsIPv6; } }
}
ChrisW
+2  A: 

You could fake the call by introducing an intermediary. For instance:

public static class NetworkUtils
{
    private static bool? forcedIPv6Support;

    /// <summary>
    /// Use this instead of Socket.OSSupportsIPv6 to
    /// allow for testing on different operating systems.
    /// </summary>
    public static SupportsIPv6
    {
        get { return forcedIPv6Support ?? Socket.OSSupportsIPv6; }
    }

    /// <summary>Only use in testing!</summary>
    public static void ForceIPv6Support(bool? value)
    {
        forcedIPv6Support = value;
    }
}

It's not entirely pleasant, but it lets you do what you need to.

ChrisW's answer is similar, but using an interface. Normally I'd be a fan of the interface solution, but it feels like it's making the production code and configuration more complicated. My solution is certainly less "pure" but possibly more pragmatic. I'll let you judge :)

Jon Skeet
Your solution is shorter, and therefore (given "entities must not be multiplied beyond necessity") better.
ChrisW
Both are great suggestions that I may be able to build on without mucking up the prod code very much.
womp
+1  A: 

You could look at wrapping all of the environmental settings that could change in a separate class that implements an interface. This could then be passed to the static method you have written which would then call back against the interface when it needs the various settings.

This would allow you to mock the environmental settings to standardize when testing to prevent the issue you are seeing. It would also let you vary the settings to test different conditions.

e.g.

public interface IEnvironment
{
    public bool SupportsIPV6 { get; }
}

And then your static method becomes.

public static void DoSomething(IEnvironment environment)
{
    if(environment.SupportsIPV6)
    {
        ...
    }
}

Then provide an implementation of IEnvironment that calls against the static System.Net method to get the actual setting for use, but a mock implementation for testing with a known value.

If you don't want to pass the interface into the static method you could possibly look at implementing an object using the singleton pattern that then calls into this interface. The static method you have could then use the singleton to access whatever the current interface is although this method is a little messy so I'd probably go with the former.

E.g.

public Environment : IEnvironment
{
  private static IEnvironment current = new Environment();
  public static IEnvironment Current
  {
    get { return current; }
    set { current = value; }
  }

  public bool SupportsIPV6
  {
    return System.Net.....
  }
}

Then call into it from your static method using.

public static void DoSomething(IEnvironemnt environment)
{
    if(Environment.Current.SupportsIPV6)
    {
        ...
    }
}

And you could change it in your tests via

Environment.Current = new MockedEnvironment();

Although you'd need to watch out if you are doing any multithreading with a singleton like that.

RobG