Search code examples
c#moqxunit

How to run specific unit tests only when running on a specific operating system?


Given the following example helper

public static class RuntimeHelper
{
    public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
    public static bool IsMac => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
    public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
}

There are several questions

  • Should I even unit test such fields? (this might be opinion based)
  • Is there a way to create unit tests based on the OS?

A sample implementation might be

public sealed class RuntimeHelperTests
{
    // IF RUNNING ON WINDOWS

    [Fact]
    public void TheOSShouldBeWindows()
    {
        Assert.True(RuntimeHelper.IsWindows);
    }

    [Fact]
    public void TheOSShouldNotBeLinux()
    {
        Assert.False(RuntimeHelper.IsLinux);
    }

    [Fact]
    public void TheOSShouldNotBeMac()
    {
        Assert.False(RuntimeHelper.IsMac);
    }

    // DO THIS FOR THE OTHER OPERATING SYSTEMS TOO
}

But those tests only pass when running on a Windows machine...


Solution

  • We can write a lot of layers in our logic. There is no practical upper limit on that. However, no matter how many layers we write, there's always one layer on the outside, which has to interact with the real world (e.g. a monitor, network, OS, file system, IO device, ...)

    It is the nature of the beast that the last layer can never truly be unit tested, because its dependencies are external resources. All of the other layers (i.e. not the last one) can be tested, because their neighboring layer can be mocked.

    In your case, RuntimeInformation is your unmockable real world dependency. This is one of the main issues with static in testing.

    Generally speaking, the advice is to then separate the business logic from the external dependency, so that you can test the business logic while wrapping the external dependency in a nice mockable layer of its own. Here is an answer of mine elaborating on exactly that.

    However, in your case, there is no real business logic. Your RuntimeHelper is already that abstracted wrapper.

    It's not meaningful to write tests for RuntimeHelper.

    Had you made RuntimeHelper instanced and not static, which I strongly recommend, then you would be able to unit test the classes that depend on RuntimeHelper by injecting a mocked RuntimeHelper which can pretend like you're on any particular OS, even when you are not.

    How to run specific unit tests only when running on a specific operating system?

    You're asking the wrong question. Rather than writing different tests for running on actually different OS'es, you should be writing the same (behavioral) tests, but in each test set the mocked RuntimeHelper to claim that you're working in a different OS.

    For example:

    [Fact]
    public void FilePathService_returns_correct_linux_path()
    {
        var mockedRuntimeHelper = new MockedRuntimeHelper(OS.Linux);
        var filePathService = new FilePathService(mockedRuntimeHelper);
    
        var result = filePathService.GetConfigurationFolder();
    
        result.Should().Be("/path/to/config");
    }
    
    [Fact]
    public void FilePathService_returns_correct_windows_path()
    {
        var mockedRuntimeHelper = new MockedRuntimeHelper(OS.Windows);
        var filePathService = new FilePathService(mockedRuntimeHelper);
    
        var result = filePathService.GetConfigurationFolder();
    
        result.Should().Be(@"C:\path\to\config");
    }
    
    [Fact]
    public void FilePathService_returns_correct_mac_path()
    {
        var mockedRuntimeHelper = new MockedRuntimeHelper(OS.Mac);
        var filePathService = new FilePathService(mockedRuntimeHelper);
    
        var result = filePathService.GetConfigurationFolder();
    
        result.Should().Be(@"whatever a Mac path is, I don't know");
    }
    

    This enables you to test all three behaviors running on any OS.

    It's still a good idea to test your code on all OS's just to be safe that there are no weird edge cases with your runtime, but that is unrelated to wanting to write specific unit tests that should only be run on specific OSes.