Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are ways to mock out a struct to get a class under unit tests?

I have a class that I'm trying to get under unit tests. The class exposes a struct as a public property. The struct also has some public methods (that do much more than a method in a struct should). I can't make changes to the struct (I don't own that code and it would be too risky at the moment).

Mock doesn't work on value types. Is there a way to effectively "mock a struct"?

public class SystemUnderTest
{
    public FragileStructCantChange myStruct {get; set;}

    public string MethodUnderTest()
    {
        if(myStruct.LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";        
    }
}

public struct FragileStructCantChange
{
    public string LongRunningMethod()
    {
        var resultString = DoStuff(); //calls other subsystems            
        return resultString;
    }
}
like image 390
Ben Joiner Avatar asked Dec 01 '25 05:12

Ben Joiner


1 Answers

Referencing a struct through an interface effectively turns it into a reference type, through a process called "boxing". Boxing a struct can cause some subtle changes in behavior, which you should read about before making your decision.

Another option is to add some indirection between the struct and the code under test. One way is to add a to specify a test value.

public class SystemUnderTest
{
    public FragileStructCantChange myStruct { get; set; }

    // This value can be overridden for testing purposes
    public string LongRunningMethodOverride { get; set; }

    public string MethodUnderTest()
    {
        // Call the indirect Func instead of referencing the struct directly
        if (LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";
    }

    private string LongRunningMethod()
        => LongRunningMethodOverride ?? myStruct.LongRunningMethod();
}

public class Tests
{

    [Fact]
    public void TestingSideDoor()
    {
        var sut = new SystemUnderTest();

        // Override the func to supply any test data you want
        sut.LongRunningMethodOverride = "successful";

        Assert.Equal("Ok", sut.MethodUnderTest());
    }
}

Another way is to expose an overridable function pointer...

public class SystemUnderTest
{
    public FragileStructCantChange myStruct { get; set; }

    // This Func can be overridden for testing purposes
    public Func<string> LongRunningMethod;

    public SystemUnderTest() {
        LongRunningMethod = () => myStruct.LongRunningMethod();
    }

    public string MethodUnderTest()
    {
        // Call the indirect Func instead of referencing the struct directly
        if (LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";
    }
}

public class Tests
{

    [Fact]
    public void TestingSideDoor()
    {
        var sut = new SystemUnderTest();

        // Override the func to supply any test data you want
        sut.LongRunningMethod = () => "successful";

        Assert.Equal("Ok", sut.MethodUnderTest());
    }
}

Another option is to use a virtual method, which can be faked either by a subclass or a mocking framework (Moq, in this example)...


public class SystemUnderTest
{
    public FragileStructCantChange myStruct { get; set; }

    // This method can be overridden for testing purposes
    public virtual string LongRunningMethod()
        => myStruct.LongRunningMethod();

    public string MethodUnderTest()
    {
        // Call the indirect method instead of referencing the struct directly
        if (LongRunningMethod() == "successful")
            return "Ok";
        else
            return "Fail";
    }
}

public class Tests
{

    [Fact]
    public void TestingSideDoor()
    {
        var sut = new Mock<SystemUnderTest>();

        // Override the method to supply any test data you want
        sut.Setup(m => m.LongRunningMethod())
            .Returns("successful");

        Assert.Equal("Ok", sut.Object.MethodUnderTest());
    }
}

I won't claim that either of these options is pretty, and I'd think hard about safety/security before putting this sort of backdoor into a shared library. An interface is usually preferable, when viable.

This sort of thing works, though, and I think it can be a fair compromise when you're faced with test-unfriendly code that you don't want to invasively refactor.

like image 193
xander Avatar answered Dec 03 '25 19:12

xander



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!