mirror of https://github.com/dapr/java-sdk.git
Unit tests for actors, no state. (#117)
* actor unit tests, no state clean up a bit * Rename class * Fix merge Co-authored-by: Artur Souza <artursouza.ms@outlook.com>
This commit is contained in:
parent
8d91f9b22c
commit
04ee301371
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package io.dapr.actors.runtime;
|
||||
|
||||
import io.dapr.actors.ActorId;
|
||||
import io.dapr.actors.client.ActorProxy;
|
||||
import io.dapr.actors.client.ActorProxyForTestsImpl;
|
||||
import io.dapr.client.DaprClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.NotSerializableException;
|
||||
import java.nio.charset.IllegalCharsetNameException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ActorNoStateTest {
|
||||
private static final AtomicInteger ACTOR_ID_COUNT = new AtomicInteger();
|
||||
|
||||
private final ActorRuntimeContext context = createContext();
|
||||
private ActorManager<ActorImpl> manager = new ActorManager<>(context);
|
||||
|
||||
public interface MyActor {
|
||||
// The test will only call the versions of this in a derived class to the user code base class.
|
||||
// The user code base class version will throw.
|
||||
Mono<String> stringInStringOut(String input);
|
||||
Mono<Boolean> stringInBooleanOut(String input);
|
||||
Mono<Void> stringInVoidOutIntentionallyThrows(String input);
|
||||
Mono<MyData> classInClassOut(MyData input);
|
||||
}
|
||||
|
||||
@ActorType(Name = "MyActor")
|
||||
public static class ActorImpl extends AbstractActor implements MyActor, Actor {
|
||||
private final ActorId id;
|
||||
private boolean activated;
|
||||
private boolean methodReturningVoidInvoked;
|
||||
|
||||
//public MyActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
public ActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
this.id = id;
|
||||
this.activated = true;
|
||||
this.methodReturningVoidInvoked = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> stringInStringOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
return s + s;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (s.equals("true")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> stringInVoidOutIntentionallyThrows(String input) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
// IllegalMonitorStateException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new IllegalMonitorStateException("IntentionalException");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MyData> classInClassOut(MyData input) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
return new MyData(
|
||||
input.getName() + input.getName(),
|
||||
input.getNum() + input.getNum());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static class MyData {
|
||||
private String name;
|
||||
private int num;
|
||||
|
||||
public MyData() {
|
||||
this.name = "";
|
||||
this.num = 0;
|
||||
}
|
||||
|
||||
public MyData(String name, int num) {
|
||||
this.name = name;
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getNum() {
|
||||
return this.num;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringInStringOut() {
|
||||
ActorProxy proxy = createActorProxy();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
Assert.assertEquals(
|
||||
"abcabc",
|
||||
proxy.invokeActorMethod("stringInStringOut", "abc", String.class).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringInBooleanOut() {
|
||||
ActorProxy proxy = createActorProxy();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
Assert.assertEquals(
|
||||
false,
|
||||
proxy.invokeActorMethod("stringInBooleanOut", "hello world", Boolean.class).block());
|
||||
|
||||
Assert.assertEquals(
|
||||
true,
|
||||
proxy.invokeActorMethod("stringInBooleanOut", "true", Boolean.class).block());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalMonitorStateException.class)
|
||||
public void stringInVoidOutIntentionallyThrows() {
|
||||
ActorProxy actorProxy = createActorProxy();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
actorProxy.invokeActorMethod("stringInVoidOutIntentionallyThrows", "hello world").block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classInClassOut() {
|
||||
ActorProxy actorProxy = createActorProxy();
|
||||
MyData d = new MyData("hi", 3);
|
||||
|
||||
// this should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
MyData response = actorProxy.invokeActorMethod("classInClassOut", d, MyData.class).block();
|
||||
|
||||
Assert.assertEquals(
|
||||
"hihi",
|
||||
response.getName());
|
||||
Assert.assertEquals(
|
||||
6,
|
||||
response.getNum());
|
||||
}
|
||||
|
||||
private static ActorId newActorId() {
|
||||
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||
}
|
||||
|
||||
private ActorProxy createActorProxy() {
|
||||
ActorId actorId = newActorId();
|
||||
|
||||
// Mock daprClient for ActorProxy only, not for runtime.
|
||||
DaprClient daprClient = mock(DaprClient.class);
|
||||
|
||||
when(daprClient.invokeActorMethod(
|
||||
eq(context.getActorTypeInformation().getName()),
|
||||
eq(actorId.toString()),
|
||||
any(),
|
||||
any()))
|
||||
.thenAnswer(invocationOnMock ->
|
||||
this.manager.invokeMethod(
|
||||
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||
invocationOnMock.getArgument(2, String.class),
|
||||
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||
invocationOnMock.getArgument(3, String.class))))
|
||||
.map(s -> {
|
||||
try {
|
||||
return context.getActorSerializer().wrapData(s.getBytes());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
|
||||
this.manager.activateActor(actorId).block();
|
||||
|
||||
return new ActorProxyForTestsImpl(
|
||||
context.getActorTypeInformation().getName(),
|
||||
actorId,
|
||||
new ActorStateSerializer(),
|
||||
daprClient);
|
||||
}
|
||||
|
||||
private static <T extends AbstractActor> ActorRuntimeContext createContext() {
|
||||
DaprClient daprClient = mock(DaprClient.class);
|
||||
|
||||
when(daprClient.registerActorTimer(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.registerActorReminder(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.unregisterActorTimer(any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.unregisterActorReminder(any(), any(), any())).thenReturn(Mono.empty());
|
||||
|
||||
return new ActorRuntimeContext(
|
||||
mock(ActorRuntime.class),
|
||||
new ActorStateSerializer(),
|
||||
new DefaultActorFactory<T>(),
|
||||
ActorTypeInformation.create(ActorImpl.class),
|
||||
daprClient,
|
||||
mock(DaprStateAsyncProvider.class)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -608,7 +608,7 @@ public class ActorStatefulTest {
|
|||
this.manager.invokeMethod(
|
||||
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||
invocationOnMock.getArgument(2, String.class),
|
||||
toStringOrNull(context.getActorSerializer().unwrapData(
|
||||
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||
invocationOnMock.getArgument(3, String.class))))
|
||||
.map(s -> {
|
||||
try {
|
||||
|
@ -632,14 +632,6 @@ public class ActorStatefulTest {
|
|||
return this.context.getActorSerializer().serializeString(params);
|
||||
}
|
||||
|
||||
private static String toStringOrNull(byte[] s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new String(s);
|
||||
}
|
||||
|
||||
private static ActorId newActorId() {
|
||||
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package io.dapr.actors.runtime;
|
||||
|
||||
import io.dapr.actors.ActorId;
|
||||
import io.dapr.actors.client.ActorProxy;
|
||||
import io.dapr.actors.client.ActorProxyForTestsImpl;
|
||||
import io.dapr.client.DaprClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.NotSerializableException;
|
||||
import java.nio.charset.IllegalCharsetNameException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DerivedActorTest {
|
||||
private static final AtomicInteger ACTOR_ID_COUNT = new AtomicInteger();
|
||||
|
||||
private final ActorRuntimeContext context = createContext();
|
||||
private ActorManager<ActorChild> manager = new ActorManager<>(context);
|
||||
|
||||
public interface MyActor {
|
||||
|
||||
// These 4 will be implemented in the user code class that extends AbstractActor, but it
|
||||
// will not be implemented in another class that will inherit that.
|
||||
Mono<String> onlyImplementedInParentStringInStringOut(String input);
|
||||
Mono<Boolean> onlyImplementedInParentStringInBooleanOut(String input);
|
||||
Mono<Void> onlyImplementedInParentStringInVoidOut(String input);
|
||||
Mono<MyData> onlyImplementedInParentClassInClassOut(MyData input);
|
||||
|
||||
// used to validate onlyImplementedInParentStringInVoidOut() was called
|
||||
boolean methodReturningVoidInvoked();
|
||||
|
||||
// The test will only call the versions of this in a derived class to the user code base class.
|
||||
// The user code base class version will throw.
|
||||
Mono<String> stringInStringOut(String input);
|
||||
Mono<Boolean> stringInBooleanOut(String input);
|
||||
Mono<Void> stringInVoidOut(String input);
|
||||
Mono<Void> stringInVoidOutIntentionallyThrows(String input);
|
||||
Mono<MyData> classInClassOut(MyData input);
|
||||
}
|
||||
|
||||
@ActorType(Name = "MyActor")
|
||||
public static class ActorParent extends AbstractActor implements MyActor, Actor {
|
||||
private final ActorId id;
|
||||
private boolean activated;
|
||||
private boolean methodReturningVoidInvoked;
|
||||
|
||||
public ActorParent(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
this.id = id;
|
||||
this.activated = true;
|
||||
this.methodReturningVoidInvoked = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> onlyImplementedInParentStringInStringOut(String input) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
return input + input + input;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> onlyImplementedInParentStringInBooleanOut(String input) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (input.equals("icecream")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> onlyImplementedInParentStringInVoidOut(String input) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
this.methodReturningVoidInvoked = true;
|
||||
System.out.println("Received " + input);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MyData> onlyImplementedInParentClassInClassOut(MyData input) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
return new MyData(
|
||||
input.getName() + input.getName() + input.getName(),
|
||||
input.getNum() + input.getNum() + input.getNum());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean methodReturningVoidInvoked() {
|
||||
return this.methodReturningVoidInvoked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> stringInStringOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
// In the cases below we intentionally only call the derived version of this.
|
||||
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new ArithmeticException("This method should not have been called");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
// In the cases below we intentionally only call the derived version of this.
|
||||
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new ArithmeticException("This method should not have been called");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> stringInVoidOut(String input) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
this.methodReturningVoidInvoked = true;
|
||||
System.out.println("Received " + input);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> stringInVoidOutIntentionallyThrows(String input) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
// IllegalMonitorStateException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new IllegalMonitorStateException("IntentionalException");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MyData> classInClassOut(MyData input) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
// In the cases below we intentionally only call the derived version of this.
|
||||
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new ArithmeticException("This method should not have been called");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActorChild extends ActorParent implements MyActor, Actor {
|
||||
private final ActorId id;
|
||||
private boolean activated;
|
||||
|
||||
public ActorChild(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
this.id = id;
|
||||
this.activated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> stringInStringOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
return s + s;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (s.equals("true")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MyData> classInClassOut(MyData input) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
return new MyData(
|
||||
input.getName() + input.getName(),
|
||||
input.getNum() + input.getNum());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static class MyData {
|
||||
private String name;
|
||||
private int num;
|
||||
|
||||
public MyData() {
|
||||
this.name = "";
|
||||
this.num = 0;
|
||||
}
|
||||
|
||||
public MyData(String name, int num) {
|
||||
this.name = name;
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getNum() {
|
||||
return this.num;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringInStringOut() {
|
||||
ActorProxy proxy = createActorProxyForActorChild();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
Assert.assertEquals(
|
||||
"abcabc",
|
||||
proxy.invokeActorMethod("stringInStringOut", "abc", String.class).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringInBooleanOut() {
|
||||
ActorProxy proxy = createActorProxyForActorChild();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
Assert.assertEquals(
|
||||
false,
|
||||
proxy.invokeActorMethod("stringInBooleanOut", "hello world", Boolean.class).block());
|
||||
|
||||
Assert.assertEquals(
|
||||
true,
|
||||
proxy.invokeActorMethod("stringInBooleanOut", "true", Boolean.class).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringInVoidOut() {
|
||||
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||
|
||||
// stringInVoidOut() has not been invoked so this is false
|
||||
Assert.assertEquals(
|
||||
false,
|
||||
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
actorProxy.invokeActorMethod("stringInVoidOut", "hello world").block();
|
||||
|
||||
Assert.assertEquals(
|
||||
true,
|
||||
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalMonitorStateException.class)
|
||||
public void stringInVoidOutIntentionallyThrows() {
|
||||
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
actorProxy.invokeActorMethod("stringInVoidOutIntentionallyThrows", "hello world").block();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classInClassOut() {
|
||||
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||
MyData d = new MyData("hi", 3);
|
||||
|
||||
// this should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
MyData response = actorProxy.invokeActorMethod("classInClassOut", d, MyData.class).block();
|
||||
|
||||
Assert.assertEquals(
|
||||
"hihi",
|
||||
response.getName());
|
||||
Assert.assertEquals(
|
||||
6,
|
||||
response.getNum());
|
||||
}
|
||||
|
||||
// The actor methods this test invokes are all implemented in ActorParent only. We're asserting it's callable when the actor proxy is for an ActorChild.
|
||||
@Test
|
||||
public void testInheritedActorMethods() {
|
||||
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||
|
||||
Assert.assertEquals(
|
||||
"www",
|
||||
actorProxy.invokeActorMethod("onlyImplementedInParentStringInStringOut", "w", String.class).block());
|
||||
|
||||
Assert.assertEquals(
|
||||
true,
|
||||
actorProxy.invokeActorMethod("onlyImplementedInParentStringInBooleanOut", "icecream", Boolean.class).block());
|
||||
|
||||
// onlyImplementedInParentStringInVoidOut() has not been invoked so this is false
|
||||
Assert.assertEquals(
|
||||
false,
|
||||
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||
|
||||
actorProxy.invokeActorMethod("onlyImplementedInParentStringInVoidOut", "icecream", Boolean.class).block();
|
||||
|
||||
// now it should return true.
|
||||
Assert.assertEquals(
|
||||
true,
|
||||
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||
|
||||
MyData d = new MyData("hi", 3);
|
||||
MyData response = actorProxy.invokeActorMethod("onlyImplementedInParentClassInClassOut", d, MyData.class).block();
|
||||
|
||||
Assert.assertEquals(
|
||||
"hihihi",
|
||||
response.getName());
|
||||
Assert.assertEquals(
|
||||
9,
|
||||
response.getNum());
|
||||
}
|
||||
|
||||
private static ActorId newActorId() {
|
||||
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||
}
|
||||
|
||||
private ActorProxy createActorProxyForActorChild() {
|
||||
ActorId actorId = newActorId();
|
||||
|
||||
// Mock daprClient for ActorProxy only, not for runtime.
|
||||
DaprClient daprClient = mock(DaprClient.class);
|
||||
|
||||
when(daprClient.invokeActorMethod(
|
||||
eq(context.getActorTypeInformation().getName()),
|
||||
eq(actorId.toString()),
|
||||
any(),
|
||||
any()))
|
||||
.thenAnswer(invocationOnMock ->
|
||||
this.manager.invokeMethod(
|
||||
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||
invocationOnMock.getArgument(2, String.class),
|
||||
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||
invocationOnMock.getArgument(3, String.class))))
|
||||
.map(s -> {
|
||||
try {
|
||||
return context.getActorSerializer().wrapData(s.getBytes());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
|
||||
this.manager.activateActor(actorId).block();
|
||||
|
||||
return new ActorProxyForTestsImpl(
|
||||
context.getActorTypeInformation().getName(),
|
||||
actorId,
|
||||
new ActorStateSerializer(),
|
||||
daprClient);
|
||||
}
|
||||
|
||||
private static <T extends AbstractActor> ActorRuntimeContext createContext() {
|
||||
DaprClient daprClient = mock(DaprClient.class);
|
||||
|
||||
when(daprClient.registerActorTimer(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.registerActorReminder(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.unregisterActorTimer(any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.unregisterActorReminder(any(), any(), any())).thenReturn(Mono.empty());
|
||||
|
||||
return new ActorRuntimeContext(
|
||||
mock(ActorRuntime.class),
|
||||
new ActorStateSerializer(),
|
||||
new DefaultActorFactory<T>(),
|
||||
ActorTypeInformation.create(ActorChild.class),
|
||||
daprClient,
|
||||
mock(DaprStateAsyncProvider.class)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package io.dapr.actors.runtime;
|
||||
|
||||
import io.dapr.actors.ActorId;
|
||||
import io.dapr.actors.client.ActorProxy;
|
||||
import io.dapr.actors.client.ActorProxyForTestsImpl;
|
||||
import io.dapr.client.DaprClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.NotSerializableException;
|
||||
import java.nio.charset.IllegalCharsetNameException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ThrowFromPreAndPostActorMethodsTest {
|
||||
private static final AtomicInteger ACTOR_ID_COUNT = new AtomicInteger();
|
||||
|
||||
private final ActorRuntimeContext context = createContext();
|
||||
private ActorManager<ActorChild> manager = new ActorManager<>(context);
|
||||
|
||||
public interface MyActor {
|
||||
Mono<Boolean> stringInBooleanOut(String input);
|
||||
}
|
||||
|
||||
@ActorType(Name = "MyActor")
|
||||
public static class ActorParent extends AbstractActor implements MyActor, Actor {
|
||||
private final ActorId id;
|
||||
private boolean activated;
|
||||
private boolean methodReturningVoidInvoked;
|
||||
|
||||
public ActorParent(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
this.id = id;
|
||||
this.activated = true;
|
||||
this.methodReturningVoidInvoked = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> onPreActorMethodInternal(ActorMethodContext actorMethodContext) {
|
||||
// IllegalMonitorStateException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new IllegalMonitorStateException("Intentional throw from onPreActorMethodInternal");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
// In the cases below we intentionally only call the derived version of this.
|
||||
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||
// to collide with something else.
|
||||
throw new ArithmeticException("This method should not have been called");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActorChild extends ActorParent implements MyActor, Actor {
|
||||
private final ActorId id;
|
||||
private boolean activated;
|
||||
|
||||
public ActorChild(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||
super(runtimeContext, id);
|
||||
this.id = id;
|
||||
this.activated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||
return Mono.fromSupplier(() -> {
|
||||
if (s.equals("true")) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static class MyData {
|
||||
private String name;
|
||||
private int num;
|
||||
|
||||
public MyData() {
|
||||
this.name = "";
|
||||
this.num = 0;
|
||||
}
|
||||
|
||||
public MyData(String name, int num) {
|
||||
this.name = name;
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getNum() {
|
||||
return this.num;
|
||||
}
|
||||
}
|
||||
|
||||
// IllegalMonitorStateException should be intentionally thrown. This type was chosen for this test just because
|
||||
// it is unlikely to collide.
|
||||
@Test(expected = IllegalMonitorStateException.class)
|
||||
public void stringInBooleanOut1() {
|
||||
ActorProxy proxy = createActorProxyForActorChild();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
Assert.assertEquals(
|
||||
false,
|
||||
proxy.invokeActorMethod("stringInBooleanOut", "hello world", Boolean.class).block());
|
||||
}
|
||||
|
||||
// IllegalMonitorStateException should be intentionally thrown. This type was chosen for this test just because
|
||||
// it is unlikely to collide.
|
||||
@Test(expected = IllegalMonitorStateException.class)
|
||||
public void stringInBooleanOut2() {
|
||||
ActorProxy proxy = createActorProxyForActorChild();
|
||||
|
||||
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||
Assert.assertEquals(
|
||||
true,
|
||||
proxy.invokeActorMethod("stringInBooleanOut", "true", Boolean.class).block());
|
||||
}
|
||||
|
||||
private static ActorId newActorId() {
|
||||
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||
}
|
||||
|
||||
private ActorProxy createActorProxyForActorChild() {
|
||||
ActorId actorId = newActorId();
|
||||
|
||||
// Mock daprClient for ActorProxy only, not for runtime.
|
||||
DaprClient daprClient = mock(DaprClient.class);
|
||||
|
||||
when(daprClient.invokeActorMethod(
|
||||
eq(context.getActorTypeInformation().getName()),
|
||||
eq(actorId.toString()),
|
||||
any(),
|
||||
any()))
|
||||
.thenAnswer(invocationOnMock ->
|
||||
this.manager.invokeMethod(
|
||||
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||
invocationOnMock.getArgument(2, String.class),
|
||||
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||
invocationOnMock.getArgument(3, String.class) )))
|
||||
.map(s -> {
|
||||
try {
|
||||
return context.getActorSerializer().wrapData(s.getBytes());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
|
||||
this.manager.activateActor(actorId).block();
|
||||
|
||||
return new ActorProxyForTestsImpl(
|
||||
context.getActorTypeInformation().getName(),
|
||||
actorId,
|
||||
new ActorStateSerializer(),
|
||||
daprClient);
|
||||
}
|
||||
|
||||
private static <T extends AbstractActor> ActorRuntimeContext createContext() {
|
||||
DaprClient daprClient = mock(DaprClient.class);
|
||||
|
||||
when(daprClient.registerActorTimer(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.registerActorReminder(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.unregisterActorTimer(any(), any(), any())).thenReturn(Mono.empty());
|
||||
when(daprClient.unregisterActorReminder(any(), any(), any())).thenReturn(Mono.empty());
|
||||
|
||||
return new ActorRuntimeContext(
|
||||
mock(ActorRuntime.class),
|
||||
new ActorStateSerializer(),
|
||||
new DefaultActorFactory<T>(),
|
||||
ActorTypeInformation.create(ActorChild.class),
|
||||
daprClient,
|
||||
mock(DaprStateAsyncProvider.class)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
package io.dapr.actors.runtime;
|
||||
|
||||
import io.dapr.actors.ActorId;
|
||||
import io.dapr.client.DaprClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
class Utilities {
|
||||
static String toStringOrNull(byte[] s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new String(s);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue