Unit Testing – Using isolation frameworks

Unit Testing – Using isolation frameworks

The SLProtocol interface is heavily used in protocols as it acts as the interface between SLScripting (the process executing QActions) and SLProtocol (the process executing the protocol logic).

Therefore, it is very likely you will end up in a situation where you want to test a method that has a dependency on the SLProtocol interface. This article explains how to fake SLProtocol so you can easily create unit tests that have a dependency on this interface.

Faking SLProtocol using MOQ

Consider for example the following method:

public string Rewrite(SLProtocol protocol, string path)
{
    if(path == null)
    {
        throw new ArgumentNullException(nameof(path));
    }

    if(String.IsNullOrWhiteSpace(path))
    {
        throw new ArgumentException("Path is empty or white space.", nameof(path));
    }

    string[] pathParts = path.Split('\\');
    string[] abbreviatedPath = new string[pathParts.Length];

    for (int i = 0; i < pathParts.Length; i++)
    {
        string part = pathParts[i];

        if (part == "Generic")
        {
            abbreviatedPath[i] = "SLCGeneric";
            protocol.Log("QA" + protocol.QActionID + "|Rewrite|Path '" + path + "' must not contain 'Generic'. Auto-adjusting to 'SLCGeneric'.", LogType.Error, LogLevel.NoLogging);
            continue;
        }

        if (itemsToAbbreviate.Contains(part))
        {
            abbreviatedPath[i] = Convert.ToString(part[0]);
        }
        else
        {
            abbreviatedPath[i] = part;
        }
    }

    return String.Join(@"\", abbreviatedPath);
}

This method is very similar to the method shown in the previous blog post about “Creating unit tests using the MSTestv2 framework in Visual Studio“. However, note that the method now has an additional parameter of type SLProtocol.

Suppose you now want to create a unit test for this method. When calling the Rewrite method in the Act step of your test method, you should pass along something that implements SLProtocol.

One option is to create your own class that implements the SLProtocol interface. However, an easier alternative is to make use of an isolation framework.

For this, you can for example use the Moq library. To start using the Moq library, select your test project in the Solution Explorer, right-click it, and then select Manage NuGet Packages. Click Browse and type Moq. Select the package and click the Install button.

using the Moq library

The best way to explain how this framework works is by using it through an example.

In the Arrange step, you create a new instance of Mock specifying SLProtocol for the type parameter (Note: you may need to add a reference to the SLManagedScripting DLL in your test project first). Then in the Act step, you pass fakeSLProtocol.Object as the object that implements SLProtocol.

[TestMethod()]
public void Rewrite_PathWithItemsToAbbreviate_ReturnsAbbreviatedPath()
{
    // Arrange
    PathRewriter pathRewriter = new PathRewriter();
    string path = @"Visios\Customers\Skyline\Protocols\Test";
    string expected = @"V\C\Skyline\P\Test";

    var fakeSlProtocol = new Mock<SLProtocol>();

    // Act
    string result = pathRewriter.Rewrite(fakeSlProtocol.Object, path);

    // Assert
    Assert.AreEqual(expected, result);
}

In this example, the object implementing the SLProtocol interface is used as a stub. I.e. it just acts as a replacement for the dependency we had. This is all it takes to create an SLProtocol fake.

In some tests, you may want to verify or assert something on the fake object (e.g. whether a specific method was called). Whenever this is the case, your fake is referred to as a mock.

For example, suppose you want to create an additional unit test to ensure that the Log method gets called whenever “Generic” is part of the specified path. This can be done as follows:

[TestMethod()]
public void AbbreviatePath_PathContainingGeneric_CallsLogMethod()
{
    // Arrange
    PathRewriter pathRewriter = new PathRewriter();
    string path = @"Visios\Customers\Generic";

    var slProtocolMock = new Mock<SLProtocol>();

    // Act
    pathRewriter.Rewrite(slProtocolMock.Object, path);

    // Assert
    slProtocolMock.Verify(p => p.Log(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>()));
}

By calling the Verify method on the slProtocolMock object, you can verify whether the Log method has been called. In this case, the values that were provided when the Log method was invoked are not important (hence the use of It.IsAny<T>). However, it is possible to also verify the values if this is needed. The following example verifies the message:

[TestMethod()]
public void AbbreviatePath_PathContainingGeneric_CallsLogMethod()
{
    // Arrange
    PathRewriter pathRewriter = new PathRewriter();
    string path = @"Visios\Customers\Generic";

    var slProtocolMock = new Mock<SLProtocol>();
    slProtocolMock.Setup(p => p.QActionID).Returns(10);

    string expectedMessage = "QA10|AbbreviatePath|Path must not contain 'Generic'. Auto-adjusting to 'SLCGeneric'.";

    // Act
    pathRewriter.Rewrite(slProtocolMock.Object, path);

    // Assert
    slProtocolMock.Verify(p => p.Log(It.IsAny<int>(), It.IsAny<int>(), expectedMessage));
}

Note the use of the Setup method on the mock object. This allows you to specify what should be returned when the QActionID property is called.

The use of isolation frameworks like Moq allows you to easily create stubs and mocks. It allows you to perform advanced assertions on the mock objects such as verifying the number of times a method got invoked, etc.

Note that you may have noticed something unusual in this example: In the Rewrite method, we invoke the following overload of the Log method: Log(string, LogType, LogLevel). However, the verify call in the Assert step looks for an invocation of the following overload of LogLog(int, int, string).

This is because currently the Log(string, LogType,  LogLevel) method is defined as an extension method. Even though this extension method extends the SLProtocol interface, it is not part of the interface. However, this method just calls the Log(int, int, string) method internally (which is defined as part of the SLProtocol interface). Therefore, we can verify whether the Log(string, LogType, LogLevel) method was called by checking whether the Log(int, int, string) method was called.

Currently, several extension methods on the SLProtocol interface are defined. Refer to NotifyProtocol class and ProtocolExtenders class for more information about these methods.

Useful links

For more information about the Moq framework, refer to:

10 thoughts on “Unit Testing – Using isolation frameworks

  1. Jan Vanhove

    Great one, a must-see for anybody integrating systems. I also see the ‘protocol as a solution’ in your Visual Studio, this nicely helps you saving your entire test setup.

  2. Saddam Zourob

    Thanks, Jan. That is really helpful.

    I was trying to mock the SLProtocolExt in the same way but I keep getting the following error:
    “Non-overridable members (here: SLProtocolExt.set_[Param_Name]) may not be used in setup / verification expressions.”

    We will need to be able to mock the SLProtocolExt as we are using it to mostly retrieve and set all parameters.

    Is there a way to do that?

  3. Pedro Debevere Post author

    Hi

    Since DataMiner 10.0.1, the SLProtocolExt class has been converted into an interface. Is it possible you are using a DataMiner version prior to 10.0.1? In that case, SLProtocolExt is still a class which would explain the error you got.
    (In Visual Studio, press F12 on SLProtocolExt to navigate to the type definition to verify whether it’s defined as a class or interface).

    If it’s an interface, you should be able to do something as follows:
    slProtocolMock.SetupGet(x => x.Exampleparameter).Returns(“myTestValue”);
    slProtocolMock.SetupSet(x => x.Exampleparameter = “MyExpectedValue”).Verifiable();

  4. Saddam Zourob

    Thanks Pedro,

    We are actually running on DataMiner 9.6.10 and using the Class Library version 1.1.2.8.

    From the Helper file, I can see the following:
    public interface ISLProtocolExt : ISLProtocol {}
    public class SLProtocolExt : SLProtocol, ISLProtocolExt {}

    which, I think, means that it is a Class that is implementing an interface!

    I am still getting the error:
    “System.NotSupportedException: Unsupported expression: sl => sl.[Param_Name]
    Non-overridable members (here: SLProtocolExt.get_[Param_Name]) may not be used in setup / verification expressions.”

  5. Pedro Debevere Post author

    Hi

    That’s indeed correct. Prior to DataMiner 10.0.1, SLProtocol was a class. To be future proof related to your unit tests, I think your best option would be to develop against a more recent version of DataMiner (Feature release 10.1.2 or above/Main release 10.2.0 or above).

    If this is not possible, as a workaround, you could try the following:
    In Visual Studio go to DIS > Settings…. and select the DLLs tab. There remove the paths. Then create a new solution. This solution should now use DataMiner DLLs that are shipped as part of DIS (this allows developing with DIS without needing a DataMiner agent) instead of the DLLs of your local agent.

  6. Saddam Zourob

    Hi Pedro,
    Thanks, that is really helpful.
    I think it would be very useful to add these details to this blog or another blog that explains the constraints of using SLProtocolExt and how it can be mocked.

    I created a new solution as you mentioned and changed my DIS settings to use class library version 1.2.0.5. I noticed that the SLProtocolExt is now generated as an interface in the Helper file. However, I get the following error when trying to run the test:

    “System.TypeLoadException: Could not load type ‘Skyline.DataMiner.Scripting.SLProtocolExt’ from assembly ‘QAction_Helper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’ because it attempts to implement a class as an interface.”

    I believe that we cannot actually run the Protocol on our DM 9.6.10 as the used class library is only compatible with DM 10, correct?

    Thanks,

  7. Pedro Debevere Post author

    Hi
    Note that no changes were required related to the DIS settings for the class library version. As you are using DataMiner 9.6.10, you should indeed use the 1.1.2.X range of the class library. Only the entries for the DLL paths under DIS > Settings > Dlls (which by default refers to C:\Skyline DataMiner\ProtocolScripts and File) should be removed before creating a new solution. This way it will use the DataMiner DLLs shipped with DIS.
    I’m not sure why the TypeLoadException is being thrown. When using the approach described above all projects should start using the DataMiner DLLs from DIS, including the QAction_Helper project.

  8. Saddam Zourob

    Hi Pedro,
    Yes, The tests are now working fine. However, publishing the protocol (after using the local DM DLLs) is causing an issue on the DMA as the SLManagedScripting.dll of the DMA is different from the one that we are currently referencing from the local DLLs in our QActions.

    For example, ConcreteSLProtocolExt is inheriting the class ConcreteSLProtocol which only exists in the new (local) SLManagedScripting.dll and does not exist in the DLL files on the DMA!

    If we decide to only let the Unit Tests project point at the local SLManagedScripting.dll, then we cannot include the Helper class as it is built upon the remote SLManagedScripting.dll file (which does not recognise the ConcreteSLProtocolExt!

    We use the ConcreteSLProtocolExt when building the subscriptions within an enhanced service as follow:

    public void Start(ConcreteSLProtocolExt protocol)
    {
    var sub = GetSubscriptionsManager(protocol);
    InitializeTables(protocol);
    sub.Subscribe((ProtocolLink)protocol);
    }

    We might need to update the DMA to start having the new SLManagedScripting.dll, which will then allow the ConcreteSlProtocolExt code in the published Protocols.

    If that is not the right way, could you advise us on how we can progress with this, please?

  9. Pedro Debevere Post author

    Hi Saddam,
    I would advise to avoid using ConcreteSLProtocol(Ext) in your code and only use SLProtocol(Ext) instead. On older DataMiner versions, SLProtocol/SLProtocolExt are concrete classes, on newer DataMiner versions these are interfaces (allowing to fake it in tests). When an older version of DataMiner tries to compile the code it should work as long as you do not mention ConcreteSLProtocol(Ext) in your code (because, as you already pointed out, these classes do not exist on newer DataMiner versions).

Leave a Reply