From 04ee3013719bf96cb4b1cced85f08b61dcea7eff Mon Sep 17 00:00:00 2001 From: Leon Mai Date: Fri, 17 Jan 2020 15:14:05 -0800 Subject: [PATCH] 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 --- .../dapr/actors/runtime/ActorNoStateTest.java | 223 +++++++++++ .../actors/runtime/ActorStatefulTest.java | 10 +- .../dapr/actors/runtime/DerivedActorTest.java | 377 ++++++++++++++++++ .../ThrowFromPreAndPostActorMethodsTest.java | 195 +++++++++ .../io/dapr/actors/runtime/Utilities.java | 33 ++ 5 files changed, 829 insertions(+), 9 deletions(-) create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/Utilities.java diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java new file mode 100644 index 000000000..47b1d0e6c --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java @@ -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 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 stringInStringOut(String input); + Mono stringInBooleanOut(String input); + Mono stringInVoidOutIntentionallyThrows(String input); + Mono 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 stringInStringOut(String s) { + return Mono.fromSupplier(() -> { + return s + s; + } + ); + } + + @Override + public Mono stringInBooleanOut(String s) { + return Mono.fromSupplier(() -> { + if (s.equals("true")) { + return true; + } else { + return false; + } + }); + } + + @Override + public Mono 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 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 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(), + ActorTypeInformation.create(ActorImpl.class), + daprClient, + mock(DaprStateAsyncProvider.class) + ); + } +} diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java index ae14cb248..4b42cea01 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java @@ -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())); } diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java new file mode 100644 index 000000000..12f05967f --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java @@ -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 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 onlyImplementedInParentStringInStringOut(String input); + Mono onlyImplementedInParentStringInBooleanOut(String input); + Mono onlyImplementedInParentStringInVoidOut(String input); + Mono 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 stringInStringOut(String input); + Mono stringInBooleanOut(String input); + Mono stringInVoidOut(String input); + Mono stringInVoidOutIntentionallyThrows(String input); + Mono 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 onlyImplementedInParentStringInStringOut(String input) { + return Mono.fromSupplier(() -> { + return input + input + input; + }); + } + + @Override + public Mono onlyImplementedInParentStringInBooleanOut(String input) { + return Mono.fromSupplier(() -> { + if (input.equals("icecream")) { + return true; + } else { + return false; + } + }); + } + + @Override + public Mono onlyImplementedInParentStringInVoidOut(String input) { + return Mono.fromRunnable(() -> { + this.methodReturningVoidInvoked = true; + System.out.println("Received " + input); + }); + } + + @Override + public Mono 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 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 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 stringInVoidOut(String input) { + return Mono.fromRunnable(() -> { + this.methodReturningVoidInvoked = true; + System.out.println("Received " + input); + }); + } + + @Override + public Mono 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 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 stringInStringOut(String s) { + return Mono.fromSupplier(() -> { + return s + s; + } + ); + } + + @Override + public Mono stringInBooleanOut(String s) { + return Mono.fromSupplier(() -> { + if (s.equals("true")) { + return true; + } else { + return false; + } + }); + } + + @Override + public Mono 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 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(), + ActorTypeInformation.create(ActorChild.class), + daprClient, + mock(DaprStateAsyncProvider.class) + ); + } +} diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java new file mode 100644 index 000000000..d783337d0 --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java @@ -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 manager = new ActorManager<>(context); + + public interface MyActor { + Mono 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 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 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 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 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(), + ActorTypeInformation.create(ActorChild.class), + daprClient, + mock(DaprStateAsyncProvider.class) + ); + } +} diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/Utilities.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/Utilities.java new file mode 100644 index 000000000..4d9fd893d --- /dev/null +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/Utilities.java @@ -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); + } +}