diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 8db71b8f3..fd3afada3 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -132,3 +132,7 @@ jobs:
working-directory: ./examples
run: |
mm.py ./src/main/java/io/dapr/examples/secrets/README.md
+ - name: Validate unit testing example
+ working-directory: ./examples
+ run: |
+ mm.py ./src/main/java/io/dapr/examples/unittesting/README.md
diff --git a/README.md b/README.md
index 604a17d29..06dc60feb 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,7 @@ Try the following examples to learn more about Dapr's Java SDK:
* [Secrets management](./examples/src/main/java/io/dapr/examples/secrets)
* [Distributed tracing with OpenTelemetry SDK](./examples/src/main/java/io/dapr/examples/tracing)
* [Exception handling](./examples/src/main/java/io/dapr/examples/exception)
+* [Unit testing](./examples/src/main/java/io/dapr/examples/unittesting)
#### API Documentation
diff --git a/examples/pom.xml b/examples/pom.xml
index b9f564e8c..7de3df109 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -89,6 +89,24 @@
opentelemetry-exporter-zipkin
${opentelemetry.version}
+
+ org.junit.jupiter
+ junit-jupiter
+ RELEASE
+ compile
+
+
+ org.mockito
+ mockito-core
+ 3.6.0
+ compile
+
+
+ org.junit.platform
+ junit-platform-console-standalone
+ 1.7.0
+ compile
+
io.dapr
dapr-sdk-springboot
diff --git a/examples/src/main/java/io/dapr/examples/bindings/http/README.md b/examples/src/main/java/io/dapr/examples/bindings/http/README.md
index 1793425be..cdef3ccf4 100644
--- a/examples/src/main/java/io/dapr/examples/bindings/http/README.md
+++ b/examples/src/main/java/io/dapr/examples/bindings/http/README.md
@@ -116,7 +116,7 @@ expected_stdout_lines:
- '== APP == Received message through binding: {"message":"Message #2"}'
- '== APP == Received message through binding: "Message #3"'
background: true
-sleep: 5
+sleep: 10
-->
```bash
diff --git a/examples/src/main/java/io/dapr/examples/state/README.md b/examples/src/main/java/io/dapr/examples/state/README.md
index 6667ddca6..9af078b75 100644
--- a/examples/src/main/java/io/dapr/examples/state/README.md
+++ b/examples/src/main/java/io/dapr/examples/state/README.md
@@ -67,9 +67,9 @@ public class StateClient {
// execute transaction
List> operationList = new ArrayList<>();
operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT,
- new State<>(myClass, FIRST_KEY_NAME, "")));
+ new State<>(FIRST_KEY_NAME, myClass, "")));
operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT,
- new State<>(secondState, SECOND_KEY_NAME, "")));
+ new State<>(SECOND_KEY_NAME, secondState, "")));
client.executeStateTransaction(STATE_STORE_NAME, operationList).block();
@@ -143,15 +143,15 @@ expected_stdout_lines:
- "== APP == Updating previous state and adding another state 'test state'... "
- "== APP == Saving updated class with message: my message updated"
- "== APP == Retrieved messages using bulk get:"
- - "== APP == StateKeyValue{value=my message updated, key='myKey', etag='2', metadata={'{}'}, error='null', options={'null'}}"
- - "== APP == StateKeyValue{value=test message, key='myKey2', etag='1', metadata={'{}'}, error='null', options={'null'}}"
+ - "== APP == StateKeyValue{key='myKey', value=my message updated, etag='2', metadata={'{}'}, error='null', options={'null'}}"
+ - "== APP == StateKeyValue{key='myKey2', value=test message, etag='1', metadata={'{}'}, error='null', options={'null'}}"
- "== APP == Deleting states..."
- "== APP == Verify delete key request is aborted if an etag different from stored is passed."
- "== APP == Expected failure. ABORTED"
- "== APP == Trying to delete again with correct etag."
- "== APP == Trying to retrieve deleted states:"
- - "== APP == StateKeyValue{value=null, key='myKey', etag='null', metadata={'{}'}, error='null', options={'null'}}"
- - "== APP == StateKeyValue{value=null, key='myKey2', etag='null', metadata={'{}'}, error='null', options={'null'}}"
+ - "== APP == StateKeyValue{key='myKey', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}}"
+ - "== APP == StateKeyValue{key='myKey2', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}}"
- "== APP == Done"
background: true
sleep: 5
@@ -181,9 +181,9 @@ Once running, the OutputBindingExample should print the output as follows:
== APP == Retrieved messages using bulk get:
-== APP == StateKeyValue{value=my message updated, key='myKey', etag='2', metadata={'{}'}, error='null', options={'null'}}
+== APP == StateKeyValue{key='myKey', value=my message updated, etag='2', metadata={'{}'}, error='null', options={'null'}}
-== APP == StateKeyValue{value=test message, key='myKey2', etag='1', metadata={'{}'}, error='null', options={'null'}}
+== APP == StateKeyValue{key='myKey2', value=test message, etag='1', metadata={'{}'}, error='null', options={'null'}}
== APP == Deleting states...
@@ -195,9 +195,9 @@ Once running, the OutputBindingExample should print the output as follows:
== APP == Trying to retrieve deleted states:
-== APP == StateKeyValue{value=null, key='myKey', etag='null', metadata={'{}'}, error='null', options={'null'}}
+== APP == StateKeyValue{key='myKey', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}}
-== APP == StateKeyValue{value=null, key='myKey2', etag='null', metadata={'{}'}, error='null', options={'null'}}
+== APP == StateKeyValue{key='myKey2', value=null, etag='null', metadata={'{}'}, error='null', options={'null'}}
== APP == Done
diff --git a/examples/src/main/java/io/dapr/examples/state/StateClient.java b/examples/src/main/java/io/dapr/examples/state/StateClient.java
index e467edb50..ac54d012e 100644
--- a/examples/src/main/java/io/dapr/examples/state/StateClient.java
+++ b/examples/src/main/java/io/dapr/examples/state/StateClient.java
@@ -72,9 +72,9 @@ public class StateClient {
// execute transaction
List> operationList = new ArrayList<>();
operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT,
- new State<>(myClass, FIRST_KEY_NAME, "")));
+ new State<>(FIRST_KEY_NAME, myClass, "")));
operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT,
- new State<>(secondState, SECOND_KEY_NAME, "")));
+ new State<>(SECOND_KEY_NAME, secondState, "")));
client.executeStateTransaction(STATE_STORE_NAME, operationList).block();
diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprExampleTest.java
new file mode 100644
index 000000000..87f47e9d9
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprExampleTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ */
+
+package io.dapr.examples.unittesting;
+
+import io.dapr.actors.ActorId;
+import io.dapr.actors.ActorType;
+import io.dapr.actors.client.ActorClient;
+import io.dapr.actors.client.ActorProxyBuilder;
+import io.dapr.client.DaprClient;
+import io.dapr.client.domain.State;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import reactor.core.publisher.Mono;
+
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * 1. Build and install jars:
+ * mvn clean install
+ * 2. cd [repo root]/examples
+ * 3. send a message to be saved as state:
+ * java -jar target/dapr-java-sdk-examples-exec.jar \
+ * org.junit.platform.console.ConsoleLauncher --select-class=io.dapr.examples.unittesting.DaprExampleTest
+ */
+public class DaprExampleTest {
+
+ @ActorType(name = "MyActor")
+ public interface MyActor {
+ String hello();
+ }
+
+ private static final class MyApp {
+
+ private final DaprClient daprClient;
+
+ private final Function actorProxyFactory;
+
+ /**
+ * Example of constructor that can be used for production code.
+ * @param client Dapr client.
+ * @param actorClient Dapr Actor client.
+ */
+ public MyApp(DaprClient client, ActorClient actorClient) {
+ this.daprClient = client;
+ this.actorProxyFactory = (actorId) -> new ActorProxyBuilder<>(MyActor.class, actorClient).build(actorId);
+ }
+
+ /**
+ * Example of constructor that can be used for test code.
+ * @param client Dapr client.
+ * @param actorProxyFactory Factory method to create actor proxy instances.
+ */
+ public MyApp(DaprClient client, Function actorProxyFactory) {
+ this.daprClient = client;
+ this.actorProxyFactory = actorProxyFactory;
+ }
+
+ public String getState() {
+ return daprClient.getState("appid", "statekey", String.class).block().getValue();
+ }
+
+ public String invokeActor() {
+ MyActor proxy = actorProxyFactory.apply(new ActorId("myactorId"));
+ return proxy.hello();
+ }
+ }
+
+ @Test
+ public void testGetState() {
+ DaprClient daprClient = Mockito.mock(DaprClient.class);
+ Mockito.when(daprClient.getState("appid", "statekey", String.class)).thenReturn(
+ Mono.just(new State<>("statekey", "myvalue", "1")));
+
+ MyApp app = new MyApp(daprClient, (ActorClient) null);
+
+ String value = app.getState();
+
+ assertEquals("myvalue", value);
+ }
+
+ @Test
+ public void testInvokeActor() {
+ MyActor actorMock = Mockito.mock(MyActor.class);
+ Mockito.when(actorMock.hello()).thenReturn("hello world");
+
+ MyApp app = new MyApp(null, actorId -> actorMock);
+
+ String value = app.invokeActor();
+
+ assertEquals("hello world", value);
+ }
+}
diff --git a/examples/src/main/java/io/dapr/examples/unittesting/README.md b/examples/src/main/java/io/dapr/examples/unittesting/README.md
new file mode 100644
index 000000000..5dc89c4d9
--- /dev/null
+++ b/examples/src/main/java/io/dapr/examples/unittesting/README.md
@@ -0,0 +1,159 @@
+## Unit testing sample
+
+This sample illustrates how applications can write unit testing with Dapr's Java SDK, JUnit 5 and Mockito.
+
+## Pre-requisites
+
+* [Dapr and Dapr Cli](https://docs.dapr.io/getting-started/install-dapr/).
+* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/).
+* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
+
+### Checking out the code
+
+Clone this repository:
+
+```sh
+git clone https://github.com/dapr/java-sdk.git
+cd java-sdk
+```
+
+Then build the Maven project:
+
+```sh
+# make sure you are in the `java-sdk` directory.
+mvn install
+```
+
+Then change into the `examples` directory:
+```sh
+cd examples
+```
+
+### Understanding the code
+This example will simulate an application code via the App class:
+
+```java
+ private static final class MyApp {
+
+ private final DaprClient daprClient;
+
+ private final Function actorProxyFactory;
+
+ /**
+ * Example of constructor that can be used for production code.
+ * @param client Dapr client.
+ * @param actorClient Dapr Actor client.
+ */
+ public MyApp(DaprClient client, ActorClient actorClient) {
+ this.daprClient = client;
+ this.actorProxyFactory = (actorId) -> new ActorProxyBuilder<>(MyActor.class, actorClient).build(actorId);
+ }
+
+ /**
+ * Example of constructor that can be used for test code.
+ * @param client Dapr client.
+ * @param actorProxyFactory Factory method to create actor proxy instances.
+ */
+ public MyApp(DaprClient client, Function actorProxyFactory) {
+ this.daprClient = client;
+ this.actorProxyFactory = actorProxyFactory;
+ }
+
+ public String getState() {
+ return daprClient.getState("appid", "statekey", String.class).block().getValue();
+ }
+
+ public String invokeActor() {
+ MyActor proxy = actorProxyFactory.apply(new ActorId("myactorId"));
+ return proxy.hello();
+ }
+ }
+```
+
+This class has two constructors. The first one can be used by production code by passing the proper instances of `DaprClient` and `ActorClient`.
+Then, it contains two methods: `getState()` will retrieve a state from `DaprClient`, while `invokeActor()` will create an instance of Actor proxy for `MyActor` interface and invoke a method on it.
+
+```java
+ @ActorType(name = "MyActor")
+ public interface MyActor {
+ String hello();
+ }
+```
+
+The first test validates the `getState()` method while mocking `DaprClient`:
+```java
+ @Test
+ public void testGetState() {
+ DaprClient daprClient = Mockito.mock(DaprClient.class);
+ Mockito.when(daprClient.getState("appid", "statekey", String.class)).thenReturn(
+ Mono.just(new State<>("statekey", "myvalue", "1")));
+
+ MyApp app = new MyApp(daprClient, (ActorClient) null);
+
+ String value = app.getState();
+
+ assertEquals("myvalue", value);
+ }
+```
+
+The second test uses a mock implementation of the factory method and checks the actor invocation by mocking the `MyActor` interface:
+```java
+ @Test
+ public void testInvokeActor() {
+ MyActor actorMock = Mockito.mock(MyActor.class);
+ Mockito.when(actorMock.hello()).thenReturn("hello world");
+
+ MyApp app = new MyApp(null, actorId -> actorMock);
+
+ String value = app.invokeActor();
+
+ assertEquals("hello world", value);
+ }
+```
+
+
+### Running the example
+
+
+Run this example with the following command:
+```bash
+java -jar target/dapr-java-sdk-examples-exec.jar org.junit.platform.console.ConsoleLauncher --select-class=io.dapr.examples.unittesting.DaprExampleTest
+```
+
+
+
+After running, Junit should print the output as follows:
+
+```txt
+╷
+├─ JUnit Jupiter ✔
+│ └─ DaprExampleTest ✔
+│ ├─ testGetState() ✔
+│ └─ testInvokeActor() ✔
+└─ JUnit Vintage ✔
+
+Test run finished after 1210 ms
+[ 3 containers found ]
+[ 0 containers skipped ]
+[ 3 containers started ]
+[ 0 containers aborted ]
+[ 3 containers successful ]
+[ 0 containers failed ]
+[ 2 tests found ]
+[ 0 tests skipped ]
+[ 2 tests started ]
+[ 0 tests aborted ]
+[ 2 tests successful ]
+[ 0 tests failed ]
+```
\ No newline at end of file
diff --git a/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java
index ea6f91840..e166f4f55 100644
--- a/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java
+++ b/sdk-tests/src/test/java/io/dapr/it/state/AbstractStateClientIT.java
@@ -140,7 +140,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
//Create deferred action to retrieve the action
- Mono> response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null),
+ Mono> response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null),
MyData.class);
//execute the retrieve of the state
State myDataResponse = response.block();
@@ -168,7 +168,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
//Create deferred action to retrieve the state
- Mono> response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null),
+ Mono> response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null),
MyData.class);
//execute the retrieve of the state
State myDataResponse = response.block();
@@ -183,7 +183,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
deleteResponse.block();
//Create deferred action to retrieve the state
- response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null), MyData.class);
+ response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null), MyData.class);
//execute the retrieve of the state
myDataResponse = response.block();
@@ -209,7 +209,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
//Create deferred action to retrieve the state
- Mono> response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null),
+ Mono> response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null),
MyData.class);
//execute the action for retrieve the state and the etag
State myDataResponse = response.block();
@@ -231,7 +231,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
- response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null), MyData.class);
+ response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null), MyData.class);
//retrive the data wihout any etag
myDataResponse = response.block();
@@ -263,7 +263,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
//Create deferred action to retrieve the state
- Mono> response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null),
+ Mono> response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null),
MyData.class);
//execute the action for retrieve the state and the etag
State myDataResponse = response.block();
@@ -285,7 +285,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
- response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null), MyData.class);
+ response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null), MyData.class);
//retrive the data wihout any etag
myDataResponse = response.block();
@@ -314,7 +314,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
//Create deferred action to get the state with the etag
- Mono> response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null),
+ Mono> response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null),
MyData.class);
//execute the get state
State myDataResponse = response.block();
@@ -355,7 +355,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
saveResponse.block();
//Create deferred action to get the state with the etag
- Mono> response = daprClient.getState(STATE_STORE_NAME, new State(stateKey, null, null),
+ Mono> response = daprClient.getState(STATE_STORE_NAME, new State<>(stateKey, (MyData) null, null),
MyData.class);
//execute the get state
State myDataResponse = response.block();
@@ -602,7 +602,7 @@ public abstract class AbstractStateClientIT extends BaseIT {
}
private State createState(String stateKey, String etag, StateOptions options, T data) {
- return new State<>(data, stateKey, etag, options);
+ return new State<>(stateKey, data, etag, options);
}
protected abstract DaprClient buildDaprClient();
diff --git a/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java b/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java
index f435d2e00..6ec47cacd 100644
--- a/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java
+++ b/sdk/src/main/java/io/dapr/client/AbstractDaprClient.java
@@ -354,7 +354,7 @@ abstract class AbstractDaprClient implements DaprClient {
*/
@Override
public Mono saveState(String storeName, String key, String etag, Object value, StateOptions options) {
- State> state = new State<>(value, key, etag, options);
+ State> state = new State<>(key, value, etag, options);
return this.saveBulkState(storeName, Collections.singletonList(state));
}
diff --git a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java b/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java
index e0976ef02..b16c8c610 100644
--- a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java
+++ b/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java
@@ -350,7 +350,7 @@ public class DaprClientGrpc extends AbstractDaprClient {
if (etag.equals("")) {
etag = null;
}
- return new State<>(value, key, etag, item.getMetadataMap(), null);
+ return new State<>(key, value, etag, item.getMetadataMap(), null);
}
private State buildStateKeyValue(
@@ -365,7 +365,7 @@ public class DaprClientGrpc extends AbstractDaprClient {
if (etag.equals("")) {
etag = null;
}
- return new State<>(value, requestedKey, etag, response.getMetadataMap(), stateOptions);
+ return new State<>(requestedKey, value, etag, response.getMetadataMap(), stateOptions);
}
/**
diff --git a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java
index ec5caa320..22d46f070 100644
--- a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java
+++ b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java
@@ -391,7 +391,7 @@ public class DaprClientHttp extends AbstractDaprClient {
byte[] data = this.stateSerializer.serialize(state.getValue());
// Custom serializer, so everything is byte[].
operations.add(new TransactionalStateOperation<>(operation.getOperation(),
- new State<>(data, state.getKey(), state.getEtag(), state.getMetadata(), state.getOptions())));
+ new State<>(state.getKey(), data, state.getEtag(), state.getMetadata(), state.getOptions())));
}
TransactionalStateRequest