Skip to content

Repository files navigation

Mocks

Description

This package provides comprehensive predefined mocks for the AXUnit testing framework. It includes simple timer mocks as well as advanced configurable mocks for complex testing scenarios with multiple timers.

Install this package

apax add @simatic-ax/mocks --dev

Namespace

Simatic.Ax.Mocks;

Available Mocks

Simple Timer Mocks

⚠️Important Limitation: Simple mocks set ALL timers in your function block to the same state. Use them only when:

  • You have a single timer in your function block, OR
  • All timers should have the same state (all elapsed or all waiting)

For testing multiple timers with different states, use ConfigurableOnDelayMock or IdentifierBasedOnDelayMock.

System.Timer Mocks (signal/duration interface)

NameOutputElapsedTimeUse Case
OnDelayMock_falseFALSE0msTimer not elapsed
OnDelayMock_trueTRUEdurationTimer elapsed
OffDelayMock_falseFALSE0msTimer not elapsed
OffDelayMock_trueTRUEdurationTimer elapsed

IEC 61131-3 Standard Mocks (IN/PT interface)

NameQ OutputETUse Case
TON_Mock_falseFALSE0msOn-Delay not elapsed
TON_Mock_trueTRUEPTOn-Delay elapsed
TOF_Mock_falseFALSE0msOff-Delay not elapsed
TOF_Mock_trueTRUEPTOff-Delay elapsed

Example - Single Timer (✅ Correct Usage):

FUNCTION_BLOCK SingleTimerController
VAR
startupTimer : OnDelay;
END_VAR
startupTimer(signal := enable, duration := T#5s);
END_FUNCTION_BLOCK
{Test}
METHOD PUBLIC Test_StartupComplete
// ✅ CORRECT: Only one timer - simple mock works perfectly
AxUnit.Mocking.Mock(NAME_OF(OnDelay), NAME_OF(OnDelayMock_true));
controller(enable := TRUE);
Equal(expected := TRUE, actual := controller.isReady);
END_METHOD

Example - All Timers Same State (✅ Correct Usage):

FUNCTION_BLOCK MultiTimerAllSame
VAR
timer1, timer2, timer3 : OnDelay;
END_VAR
// All timers must be elapsed
IF timer1.output AND timer2.output AND timer3.output THEN
allReady := TRUE;
END_IF;
END_FUNCTION_BLOCK
{Test}
METHOD PUBLIC Test_AllTimersElapsed
// ✅ CORRECT: All timers need same state (all TRUE)
AxUnit.Mocking.Mock(NAME_OF(OnDelay), NAME_OF(OnDelayMock_true));
controller(enable := TRUE);
Equal(expected := TRUE, actual := controller.allReady);
END_METHOD

Example - Mixed Timer States (❌ Wrong Usage):

FUNCTION_BLOCK MixedTimerStates
VAR
timer1, timer2, timer3 : OnDelay;
END_VAR
// Need: timer1=TRUE, timer2=FALSE, timer3=TRUE
END_FUNCTION_BLOCK
{Test}
METHOD PUBLIC Test_MixedStates
// ❌ WRONG: Simple mocks cannot create mixed states!
// All timers will be TRUE or all FALSE
AxUnit.Mocking.Mock(NAME_OF(OnDelay), NAME_OF(OnDelayMock_true));
// ✅ SOLUTION: Use ConfigurableOnDelayMock or IdentifierBasedOnDelayMock instead!
END_METHOD

📖 See SimpleMocks_RealWorld_Test.st for complete examples.

Advanced Configurable Mocks

ConfigurableOnDelayMock Family

Best for: Sequential timers with consistent call order and different states

  • Supports up to 4 timer instances with individual states
  • Call-order based identification (1st call = T1, 2nd call = T2, etc.)
  • Works with timers having identical durations
  • Simple configuration
  • Available for: OnDelay, OffDelay, TON, TOF

📖 Full Documentation

Available Mocks:

  • ConfigurableOnDelayMock + ConfigurableOnDelayMockPayload - For OnDelay
  • ConfigurableOffDelayMock + ConfigurableOffDelayMockPayload - For OffDelay
  • ConfigurableTONMock + ConfigurableTONMockPayload - For TON
  • ConfigurableTOFMock + ConfigurableTOFMockPayload - For TOF
payload.ResetCounter();
// Timer 1: elapsed
payload.T1_Enabled := TRUE;
payload.T1_Output := TRUE;
payload.T1_ElapsedTime := T#1s;
// Timer 2: waiting (different state!)
payload.T2_Enabled := TRUE;
payload.T2_Output := FALSE;
payload.T2_ElapsedTime := T#500ms;
// Timer 3: elapsed
payload.T3_Enabled := TRUE;
payload.T3_Output := TRUE;
payload.T3_ElapsedTime := T#5s;
AxUnit.Mocking.Mock(
mockeeFn := NAME_OF(OnDelay), mockFn := NAME_OF(ConfigurableOnDelayMock),
payload := payload
);

IdentifierBasedOnDelayMock Family

Best for: Conditional timer logic (e.g., IF mode=1 THEN timer2 ELSE timer3)

  • Supports up to 4 timer instances with individual states
  • Position-based identification with explicit call positions
  • Perfect for conditional timer usage
  • Duration-independent
  • Available for: OnDelay, OffDelay, TON, TOF

📖 Full Documentation

Available Mocks:

  • IdentifierBasedOnDelayMock + IdentifierBasedOnDelayMockPayload - For OnDelay
  • IdentifierBasedOffDelayMock + IdentifierBasedOffDelayMockPayload - For OffDelay
  • IdentifierBasedTONMock + IdentifierBasedTONMockPayload - For TON
  • IdentifierBasedTOFMock + IdentifierBasedTOFMockPayload - For TOF
payload.ResetCounter();
// Position 1: Always called
payload.T1_Enabled := TRUE;
payload.T1_CallPosition := 1;
payload.T1_Output := TRUE;
// Position 2: Called in mode 1
payload.T2_Enabled := TRUE;
payload.T2_CallPosition := 2;
payload.T2_Output := FALSE;
// Position 2: Called in mode 2 (same position, different timer!)
payload.T3_Enabled := TRUE;
payload.T3_CallPosition := 2;
payload.T3_Output := TRUE;
AxUnit.Mocking.Mock(
mockeeFn := NAME_OF(OnDelay), mockFn := NAME_OF(IdentifierBasedOnDelayMock),
payload := payload
);

Quick Start Examples

Example 1: Simple Single Timer Mock

USING System.Timer;
USING AxUnit.Assert;
NAMESPACE Simatic.Ax.Mocks
FUNCTION_BLOCK FunctionBlockWhichUsesTimer
VAR_INPUT
enable : BOOL;
END_VAR
VAR_OUTPUT
timerStatus : STRING;
END_VAR
VAR
ton : OnDelay;
END_VAR
ton(signal := enable, duration := T#10s);
IF (ton.output) THEN
timerStatus := 'ELAPSED';
ELSIF (ton.signal AND NOT ton.output) THEN
timerStatus := 'RUNNING';
ELSIF (NOT ton.signal) THEN
timerStatus := 'IDLE';
END_IF;
END_FUNCTION_BLOCK
{TestFixture}
CLASS TestFunctionBlockWhichUsesTimer
VAR
testInstance : FunctionBlockWhichUsesTimer;
END_VAR
{Test}
METHOD PUBLIC FunctionBlockReturnsElapsedWhenEnabledAndTimeHasElapsed
// ✅ CORRECT: Single timer - simple mock is perfect
AxUnit.Mocking.Mock(NAME_OF(OnDelay), NAME_OF(OnDelayMock_true));
testInstance(enable := TRUE);
Equal(expected := 'ELAPSED', actual := testInstance.timerStatus);
END_METHOD
END_CLASS
END_NAMESPACE

Example 2: Multiple Timers with Different States

{Test}
METHOD PUBLIC TestMultipleTimersWithDifferentStates
VAR
payload : ConfigurableOnDelayMockPayload;
END_VAR
payload.ResetCounter();
// T1: elapsed
payload.T1_Enabled := TRUE;
payload.T1_Output := TRUE;
payload.T1_ElapsedTime := T#10s;
// T2: waiting (different state!)
payload.T2_Enabled := TRUE;
payload.T2_Output := FALSE;
payload.T2_ElapsedTime := T#5s;
// T3: elapsed
payload.T3_Enabled := TRUE;
payload.T3_Output := TRUE;
payload.T3_ElapsedTime := T#30s;
AxUnit.Mocking.Mock(
mockeeFn := NAME_OF(OnDelay), mockFn := NAME_OF(ConfigurableOnDelayMock),
payload := payload
);
// Test your function block
myFB(enable1 := TRUE, enable2 := TRUE, enable3 := TRUE);
Equal(expected := TRUE, actual := myFB.OutputT1);
Equal(expected := FALSE, actual := myFB.OutputT2); // Different state!
Equal(expected := TRUE, actual := myFB.OutputT3);
END_METHOD

Example 3: Conditional Timers

{Test}
METHOD PUBLIC TestConditionalTimers
VAR
payload : IdentifierBasedOnDelayMockPayload;
END_VAR
payload.ResetCounter();
// Position 1: Always called
payload.T1_Enabled := TRUE;
payload.T1_CallPosition := 1;
payload.T1_Output := TRUE;
// Position 2: Called in mode 1 OR mode 2 (different timers, same position)
payload.T2_Enabled := TRUE;
payload.T2_CallPosition := 2;
payload.T2_Output := TRUE; // Mode 1 result
AxUnit.Mocking.Mock(
mockeeFn := NAME_OF(OnDelay), mockFn := NAME_OF(IdentifierBasedOnDelayMock),
payload := payload
);
// Test mode 1
myFB(mode := 1, enable := TRUE);
END_METHOD

Choosing the Right Mock

ScenarioRecommended Mock
Single timerOnDelayMock_true / OnDelayMock_false
Multiple timers, all same stateOnDelayMock_true / OnDelayMock_false
Multiple timers, different statesConfigurableOnDelayMock
Conditional timer logic (IF/CASE)IdentifierBasedOnDelayMock
IEC 61131-3 standard timersTON_Mock_true / TOF_Mock_false

Testing Best Practices

Use Stateless Pattern

Always use the stateless pattern to ensure test isolation:

{TestFixture}
CLASS MyTests
VAR
myFB, myFBStateless : MyFunctionBlock;
payload, payloadStateless : ConfigurableOnDelayMockPayload;
END_VAR
{TestSetup}
METHOD PUBLIC TestSetup
payload := payloadStateless;
myFB := myFBStateless;
END_METHOD
END_CLASS

Reset Counter Before Each Test

For call-order based mocks, always reset the counter:

{Test}
METHOD PUBLIC MyTest
payload.ResetCounter(); // ← Essential!
// ... configure and test ...
END_METHOD

Documentation

Tests

All mocks include comprehensive test coverage. See the test/ directory for examples:

Simple Mocks:

Configurable Mocks:

Identifier-Based Mocks:

Real-World Examples:

Contribution

Thanks for your interest in contributing. Anybody is free to report bugs, unclear documentation, and other problems regarding this repository in the Issues section or, even better, propose changes using Merge Requests.

License and Legal information

Please read the Legal information

About

No description, website, or topics provided.

Resources

Contributing

Stars

1 star

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors