blob: 0ebcc685a14967f93c28c2e3d180e8b17be9243b [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
= Testing (or using PLC4X without a PLC)
== The Mock Driver
PLC4X has a _Mock Driver_ which was initially implemented to be used for Unit Tests and this still is its main purpose.
But this driver is also very suitable to play around a bit with the PLC4X API if no _Hardware_ PLC is available.
The driver can be found in the Maven module
[subs=attributes+]
----
<dependency>
<groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-driver-mock</artifactId>
<version>{current-last-released-version}</version>
<scope>test</scope>
</dependency>
----
The connection string Syntax for the mock driver is `mock:{name-of-the-connection}`. So you can use multiple Mock Devices at the same time.
The Mock Driver does nothing else than forwarding all Requests to a `Virtual Device which we can provide to control all responses and also Monitor them, e.g. for unit tests.
The Interface for the Mock Device is
```
public interface MockDevice {
Pair<PlcResponseCode, PlcValue> read(String fieldQuery);
PlcResponseCode write(String fieldQuery, Object value);
Pair<PlcResponseCode, PlcSubscriptionHandle> subscribe(String fieldQuery);
void unsubscribe();
// ...
}
```
== Simple Example
Imagine we have some Code which we cannot control or whose functionality we want to test.
This can be done with the Mock Driver in the following way.
First, a new Mock Connection is established (like any other connection also would be):
```
PlcMockConnection connection = (PlcMockConnection) PlcDriverManager.getDefault().getConnectionManager().getConnection("mock:my-mock-connection");
```
You see, that we directly cast the Connection to a `PlcMockConnection`. This is done, because we need to _connect_ a Device to this Mock Connection.
This is done in the following Snippet
```
connection.setDevice(mockDevice);
```
Here, we pass it an instance of `MockDevice` which could be a simple Implementation of the interface like
```
MockDevice mockDevice = new MockDevice() {
Pair<PlcResponseCode, PlcValue> read(String fieldQuery) {
System.out.println("I got a read to " + fieldQuery);
return Pair.of(PlcResponseCode.OK, new PlcString("hello"));
}
PlcResponseCode write(String fieldQuery, Object value) {
System.out.println("I got a write to " + fieldQuery + " with the value " + value);
return PlcResponseCode.OK;
}
// ...
}
```
This would just return a String Value `hello` for every request and print all read and write requests to the Console.
== Unit Testing with the Mock Driver
To use the Mock driver in Unit Tests the easiest way is to generate the `MockDriver` instance as Mockito (or any other Framework) Mock.
Like in the following Example
```
MockDevice mockDevice = Mockito.mock(MockDevice.class);
PlcMockConnection connection = (PlcMockConnection) PlcDriverManager.getDefault().getConnectionManager().getConnection("mock:my-mock-connection");
connection.setDevice(mockDevice);
// Populate the Mock to avoid a NPE
when(mockDevice).read(anyString()).thenReturn(Pair.of(PlcResponseCode.OK, new PlcString("hello")));
// Some Demo code that uses the same Driver Manager and either the connection from above
// or at least mock:my-mock-connection as connection string
// Here: send a request to the field "MyAdress"
connection
.readRequestBuilder
.addItem("station", "MyAdress")
.build()
.execute()
.get();
// Check that the we really issued a Read request to the Field "MyAdress"
verify(mockDevice).read(eq("MyAdress"));
```
But as the `MockDriver` uses a static Mock Connection registry the following Code works also
```
MockDevice mockDevice = Mockito.mock(MockDevice.class);
// Setup
PlcMockConnection connection = (PlcMockConnection) PlcDriverManager.getDefault().getConnectionManager().getConnection("mock:my-mock-connection");
connection.setDevice(mockDevice);
// Populate the Mock to avoid a NPE
when(mockDevice).read(anyString()).thenReturn(Pair.of(PlcResponseCode.OK, new PlcString("hello")));
// Some Demo code that uses the same Driver Manager and either the connection from above
// or at least mock:my-mock-connection as connection string
// Here: send a request to the field "MyAdress"
// and we build up a new Connection
try (PlcConnection conn = PlcDriverManager.getDefault().getConnectionManager().getConnection("mock:my-mock-connection")) {
conn
.readRequestBuilder
.addItem("station", "MyAdress")
.build()
.execute()
.get();
} catch (Exception e) {
// do nothing
}
// Check that the we really issued a Read request to the Field "MyAdress"
verify(mockDevice).read(eq("MyAdress"));
```
The Snippet above shows that the part under test really has to share nothing with the test code except for the connection string.
== Conclusion
The above examples show that the `MockDriver` driver can not only be used to play around with the API but is also a powerful tool to
do unit testing of Code which uses the PLC4X API.
All that needs to be done is to either pass an instance of the Mocked Connection or just use the same Connection string (e.g. from a test configuration) that was used to Prepare a Mock Device.
Some Examples of further (more Complex) use cases can be found in the PLC4X Codebases, e.g. in the following classes
* `org.apache.plc4x.java.opm.PlcEntityManagerTest`
* `org.apache.plc4x.java.opm.PlcEntityManagerComplexTest`
* `org.apache.plc4x.java.scraper.ScraperTest`
and many more Test classes, especially in the OPM and the Scraper Module.