Mocking objects in TwinCAT

In my earlier posts I’ve written about development of TwinCAT software using test driven development (TDD), by writing unit tests. One of the advantages by adhering to the process of TDD is that you mostly will end up with function blocks (FBs) which have limited but well defined responsibility. Eventually you will however have FBs that are dependent on other function blocks. These could be FBs that are your own, or part of some 3rd party library, for example a Beckhoff library. Further, what if this external FB relies on some other functionality such as external communication using sockets that we have no control of? The external FBs should already be tested, we’re only interested in making sure our unit tests test our code! What do we do? A solution to this is to mock the external functionality and use dependency injection.

The technique of mocking objects is a way to abstract the external functionality and do a dependency injection of the concrete implementation of that into your function block. Huh, what did I just say? Let me explain with a few illustrations. Lets say you’re developing a function block (FB) that is supposed to deliver some results (output) based on some inputs. These inputs can be any data provided in the classical sense of VAR_INPUT, but also by a dependency from an external system by using an external FB that does some communication to this system. The conventional way of solving this is to create an direct dependency of this external FB by creating an instance of it in your FB.

Direct dependency

This is the most obvious and straightforward way of implementing dependencies to function blocks, but this causes one big problem – it’s not possible to write a unit test for your FB, because there is a direct dependency to something that we have no way of dealing with in our unit test. More importantly, FB_External is an external library and should already be tested.

The solution to our problem is to mock out the function block FB_External by something well known in the OOP-sphere called dependency injection. With dependency injection we supply the external dependency to our FB instead of our FB creating an instance of it. Now how is this “mocking” going to help us? The magic trick here is that we can create a dummy FB_External that has some pre-defined behaviour, and in our unit-test we inject this dummy instead of the real deal! Think of it this way: our function block FB_OurFB doesn’t care the slightest of how FB_External works, it just wants to have access to the service/functionality (whatever that may be) that the FB provides. If we mock this object and provide FB_OurFB with a dummy object that gives a pre-defined output, we effectively remove the direct dependency to FB_External in our unit test, and we can focus to test what is important, which is the internals of FB_OurFB.

In practice there are a few ways to accomplish this in IEC61131-3. One way that resembles how you would do it in other languages such as C++ is to provide the dependency in the constructor of the object. The FB_init method in TwinCAT3 is what most closely resembles a constructor in IEC61131-3, and it’s in this method that we can inject our dependency to the external FB. For this one could provide a pointer to the type of FB_External, but the OOP-way (which is more elegant) is to provide an interface with the functionality provided by FB_External. After all, FB_OurFB only cares about the service that FB_External provides, which is nExternalOutput : DWORD. By utilizing interfaces, we are designing our software in such a way that it’s more obvious of what services and functionality is needed for our FB to do its job. By looking at the constructor (FB_init) of our FB, we can see these dependencies.

In our real application we will inject the concrete function block FB_ExternalWrapper into FB_OurFB, because we want the real deal in the production code. But in our unit tests that are testing FB_OurFB we create a mock object implementing the same interface as FB_ExternalWrapper and inject that instead. We can give this mock object any behaviour that we find suitable for our test. As long as it gives us some pre-defined results we can focus on testing the logic of FB_OurFB.

Injected dependency

Woah, the amount of boxes escalated quickly! Before I explain each and one of them, let me explain the overall picture. What we’ve introduced is an interface which gives us the possibility to decide whether we should use a function block or a mock of it. For this to work, we need our FB_OurFB to use the interface instead, and when creating FB_OurFB we need to tell whether it should use the real external FB or a mock of it.

I_External

This is an interface that we create which has only one method “execute”. This method has the same inputs and outputs as the FB_External function block, which is the functionality that we want to mock out in our application. Why create a method in the interface and not have these in and out-parameters in the body of the interface? The answer to that is simply that this is not supported in IEC61131-3 (though I wish it was). It’s not possible to create anything such as:

INTERFACE I_External
VAR_INPUT
    bExecute : BOOL;
END_VAR
VAR_OUTPUT
    nExternalOutput : DWORD;
END_VAR

Try this and the TwinCAT compiler will complain:

As both the function blocks FB_MockFB and FB_ExternalWrapper implements this interface, we can substitute one for the other when having production code versus when running unit tests.

FB_OurFB

The change we’ve done here is that we’ve introduced the FB_init-method, which is the functionality that allows us to come as close as possible to a constructor as possible in TwinCAT3. With FB_init, we have the possibility to inject a dependency to a function block that implements the interface I_External when we create FB_OurFB. Instead of having a direct dependency to FB_External our function block now instead depends on the interface I_External, for which we can choose whether we want to inject a mock-object or the real deal.

FB_MockFB

The purpose of the mocking function block is to act as a dummy FB for the real functionality. It’s designed and implemented in such a way that it will return predictable output. The mock FB is replacing some code that already is supposed to be tested and verified. This FB does necessarily not need any internal state – one particular mock object can return the same output value every time. As it implements the I_External interface, we can inject it into the constructor of FB_OurFB.

FB_ExternalWrapper

Because the external function block does not implement our interface, we need to create a wrapper function block around it which implements the I_External interface. The only thing the FB_ExternalWrapper does is to instantiate an instance of FB_External and forward any calls to it through the method execute. This gives us a possibility to use FB_External in the production code by injecting an instance of it into FB_OurFB.

Enough with theory, I’m sure you’re eager to see an example! I’ll demonstrate this concept with an example of using the Beckhoff function block FB_EcCoeSdoRead, located in the Tc2_EtherCAT library. This post has however grown quite a lot compared to what I did foresee, so I will need to split it up into two parts in where the example will go into the next part so as always, stay tuned!

Anesthesia icon by Freepik from www.flaticon.com
Mocha & book by Rawpixel from www.pixabay.com

  • Share on:

9 Comments, RSS

  1. Sebastian

    I cant’t wait to see how you use the FB_Init method to decide wether to use FB_MockFB or FB_ExternalWrapper. I’m using interfaces a lot but I’m doing this normally as input variable.

    • Jakob Sagatowski

      Anders,

      thanks for the feedback! Yes it’s in the pipe, though progress has been a little slow 🙂

  2. Wichem

    I like your site very much!!!
    Waiting to see more.
    Did you already published the 2 article on this subject?

  3. Clayton

    Nice! I was working on something that I needed to mock but was about to give up because it seemed like too much cruft to add to my FB, but your post got me thinking about it a little differently. One thing I found is that if you’re running >= TC3.1.4024 you can put conditional pragmas in the declaration portion of the FB, so you could do something super clean like

    “`
    {IF defined (DevTest)}
    ExternalLibUtility: FB_ExternalLibUtility;
    {ELSE}
    ExternalLibUtility: Mock_ExternalLibUtility;
    {END_IF}
    “`

    Where `Mock_ExternalLibUtility` is my FB that `EXTENDS FB_ExternalLibUtility`. (Bonus of EXTEND-ing the FB you’re mocking is that you don’t need to re-declare any of the input or output variables!) `DevTest` is “defined” by putting it in the “Properties” > “Compile” > “Compiler Defines” of your library project.

    But I’m running build 4022 😢, where conditional pragmas can only be in the implementation portion of the FB. The way I’m doing it is by using a pointer to the 3rd party FB and putting the conditional pragma in my FB_init method:

    “`
    _RealExternalLibUtility: FB_ExternalLibUtility;
    _MockExternalLibUtility: Mock_ExternalLibUtility;
    ExternalLibUtility: POINTER TO FB_ExternalLibUtility := ADR(_RealExternalLibUtility);
    “`

    FB_Init:
    “`
    {IF defined (DevTest)}
    ExternalLibUtility := ADR(_MockExternalLibUtility);
    {END_IF}
    “`

    Not as clean as my first example (and sure you could clean it up more by putting {hide} pragmas on the underscore-prefixed variables), but not bad!

  4. Aliazzz

    Really informative and thank you for the clear explanation, looking forward to the next posts!

  5. Hi,

    I’m so happy that I have discovered you. I wish it was sooner…

    I was stuck trying to find way how to do DI with IEC61131-3. I gave up from Interfaces because I was always using POINTER TO type of variable in fb’s ctor. Then I have switched to ABSTRACT-ions but that made code more complicated especially with nested inheritances…

    Today I tried again with Interface, and I could not get it working until I read your article and notice that you are not using POINTER TO. Removing POINTER TO removed all compiler errors.

    Thank you.

    *What do you prefer more for supporting your work? Via gofoundme or patreon?

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.