Purpose
When testing function blocks that interact with hardware interfaces (HTTP, CAN, RS485, etc.), direct dependencies on B&R libraries or physical devices make automated testing difficult, unreliable or impossible.
This article explains how to decouple hardware access from the logic you want to test, using a dependency injection approach.
The Problem
Typical B&R function blocks directly call library functions (e.g., httpClient, or custom serial drivers).
That’s fine for runtime, but problematic for unit testing:
- The FB will always call the real hardware interfacing function block.
- The test environment lacks the required physical hardware.
- You can’t control or verify what the library call is doing.
As a result, tests can’t isolate or validate the FB’s internal logic.
The Solution: Dependency Injection via Function Pointer
Instead of calling the hardware function directly, the FB calls it through a function pointer stored in its internal structure.
This pointer can be redirected to a mock implementation during tests, while pointing to the real library function in production.
Implementation Steps
The following example uses the httpClient Function Block from B&R’s AsHttp library,
but the same pattern applies to any other hardware-dependent FUB.
Extend the Internal Structure
Add a UDINT variable to your FB’s internal structure to store a function pointer for dependency injection.
tLibFubInternal : STRUCT (*Type definition for FUBs internal data struct*)
Http : httpClient; (* http client instance *)
fpHttpClientTestMock : UDINT; (* pointer to function: void (*)(httpClient*) *)
END_STRUCT
Use the Function Pointer in C Code
Resolve the pointer into a local variable. If a mock is injected, use it — otherwise, call the real httpClient() from AsHttp.
/* ---- Function pointer typedef ---- */
/* Used to replace the B&R AsHttp httpClient function with a mock during unit testing */
typedef void (*fpHttpClient)(struct httpClient*);
void libFub(struct libFub* inst)
{
/* ---- Resolve function pointer each cycle ---- */
unsigned char useMock = (0 != inst->Internal.fpHttpClientTestMock);
fpHttpClient xHttpClient = useMock ? (fpHttpClient)inst->Internal.fpHttpClientTestMock /* injected mock */
: httpClient; /* default (real) */
/* ---- Call the resolved function ---- */
xHttpClient(&inst->Internal.Http);
}
This ensures the function pointer is safe across online changes and redundant systems, since no pointer is cached between cycles.
Write and Inject a Mock in the Unit Test
In your test environment, define a mock implementation and inject it in the _SETUP_TEST() step.
void mockHttpClient(struct httpClient* p)
{
if (!p) return;
if (p->send) {
/* emulate asynchronous behaviour */
p->status = ERR_FUB_BUSY;
p->httpStatus = 0;
} else {
/* next cycle: operation complete */
p->status = ERR_OK;
p->httpStatus = 200;
}
}
/* in _SETUP_TEST() */
mIc.Fb.Internal.fpHttpClientTestMock = (UDINT)mockHttpClient;
Your test now runs fully deterministic, without accessing the real httpClient FUB or the network stack.
Function Block Interface Stays Untouched
Because the injection field lives inside the internal structure (not the public interface):
- No change to the IEC signature or FB API
- Library dependencies (AsHttp, AsCanOpen, etc.) remain intact
- Only the unit test replaces the call dynamically at runtime
This pattern guarantees complete isolation of hardware-dependent logic while keeping the FB’s runtime behavior identical in production and in test builds.
Applying the Pattern to Different Interfaces
Mocks can simulate delayed responses, CRC errors, or timeouts, allowing you to comfortably test edge conditions without connected hardware.
Interface Example Mock behavior
| Interface | Example Mock behavior |
|---|---|
| CANopen | Emulate PDOs, Heartbeat or SDO commands |
| RS485 / Serial | Emulate received frames by injecting test data into the interface buffer |
| OPC UA | Simulate client/server callbacks or connection loss |
| HTTP / TCP | Return specific HTTP codes or timeouts (200 / 404 / 500 / timeout) |
| etc. | Any interface function can be replaced by a mock using the same pattern |
This pattern generalizes across all communication stacks — any external dependency can be replaced by a mock handler through the internal structure. That allows deterministic, fully automated testing of all interface-level PLC logic without live hardware.
Advantages & Summary
Dependency injection via the internal structure is the a reliable way to unit test hardware-dependent function blocks in B&R’s Automation Runtime.
It decouples your PLC logic from B&R libraries and physical devices — enabling real-time, deterministic, fully automated tests even for complex communication layers.
Key Benefits:
- Hardware-independent testing – No need for CAN devices, HTTP endpoints, or RS485 slaves.
- Deterministic timing – Mocked functions execute in fixed PLC cycles.
- No library overrides required – Works even if the FB is part of a precompiled library.
- Reusable pattern – Applies to any interface layer (HTTP, CAN, serial, TCP, OPC UA, …).
- CI/CD ready – Tests run headless on Automation Runtime or in your build pipeline.