Tests that cope gracefully with Airplane Mode

Have you ever needed to write code that detects if the current computer has an internet connection or not? Having recently tried this, it turns out it’s not quite as easy as I expected it would be. So since I’ve banged my head against the challenge, here’s one approach to solving the problem that you might find useful:

If you read up on automated tests, you’ll find a lot of detail on the topic of ensuring any dependencies of the code under test can be mocked. One of the key reasons given for this is that some dependencies will have non-deterministic behaviour – calls that ask for the current time or fetch data from the internet for example. While these architectural rules are good, one of the challenges of dealing with legacy code is that it usually wasn’t built with good IoC patterns. That means you can find yourself trying to work around non-deterministic dependencies while you get tests in place…

Recently I found myself looking at some legacy code which made internet requests using the WebClient class. In order to get started updating this code I needed to verify its current behaviour, so I wrote a few tests that caused it to fetch data from a known URL and verify the response.

That worked fine until I found myself sitting on a train and trying to continue my development work. Suddenly all the tests I’d written against this class failed – and failed very slowly. Unsurprisingly really, since the train I was on charged a fortune for Wifi access, so my laptop was in Airplane mode. And that caused all network requests to fail with timeout errors…

That got me thinking about how I could detect whether the laptop had a valid network connection or not. That would allow me to make these tests skip rather than fail slowly while I was offline.

In theory, the answer to this is simple. The .Net Framework includes the class NetworkInterface which has a method GetIsNetworkAvailable(). Once you get past the poor naming conventions shown there, it seems like that should solve the problem. My first attempt tried:

[TestMethod]
public void SomeClass_SomeMethod_NetworkFetch_ReturnsValidData()
{
    if(!NetworkInterface.GetIsNetworkAvailable())
    {
        Assert.Inconclusive("This test requires network access");
    }

    // rest of the test...
}

But sitting on my train with my network disabled, that call to GetIsNetworkAvailable() still returned true. Cue some head scratching…

Poking about I realised that NetworkInterface class includes another method for GetAllNetworkInterfaces(). So I fired up LinqPad and I wrote a fragment of code to list out the state of all the adapters on my laptop:

var adapters = NetworkInterface
                .GetAllNetworkInterfaces()
                .Select(i => i.Description + ": " + i.OperationalStatus.ToString())
Console.WriteLine(String.Join("\r\n", adapters);

And that returned:

Microsoft Wi-Fi Direct Virtual Adapter: Down
Intel(R) Dual Band Wireless-AC 7260: Down
Intel(R) Ethernet Connection I218-LM: Down
VirtualBox Host-Only Ethernet Adapter: Up
Software Loopback Interface 1: Up
Teredo Tunneling Pseudo-Interface: Down
Microsoft ISATAP Adapter #3: Down

So the loopback adapter and the VirtualBox network adapter are the problem – since neither of them rely on a physical network connection they are both reported as working in Airplane mode.

Knowing that, it’s pretty easy to code around the problem:

private bool physicalNetworkAvailable()
{
    return NetworkInterface.GetAllNetworkInterfaces()
        .Where(i => i.OperationalStatus == OperationalStatus.Up )
        .Select(i => i.Description.ToLower())
        .Where(n => !n.Contains("virtualbox"))
        .Where(n => !n.Contains("loopback"))
        .Any();
}

(Of course it’s possible there might be other names that need filtering on your machine)

and any tests I need to be skipped when I’m offline can be in the form:

public void SomeClass_SomeMethod_NetworkFetch_ReturnsValidData()
{
    if(!physicalNetworkAvailable())
    {
        Assert.Inconclusive("This test requires network access");
    }

    // rest of the test...
}

So now I can get back to the important work of fixing the code’s dependencies so that I don’t need the code here…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s