Skip to content

Commit 4963a40

Browse files
authored
Added Workflow.addWorkflowImplementationFactory for unit testing (#140)
* Added Worker.addWorkflowImplementationFactory * Added unit test for worker.addWorkflowImplementationFactory
1 parent 1f18a97 commit 4963a40

File tree

4 files changed

+88
-15
lines changed

4 files changed

+88
-15
lines changed

src/main/java/com/uber/cadence/internal/sync/POJOWorkflowImplementationFactory.java

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.uber.cadence.internal.worker.WorkflowExecutionException;
2929
import com.uber.cadence.testing.SimulatedTimeoutException;
3030
import com.uber.cadence.workflow.Functions;
31+
import com.uber.cadence.workflow.Functions.Func;
3132
import com.uber.cadence.workflow.QueryMethod;
3233
import com.uber.cadence.workflow.SignalMethod;
3334
import com.uber.cadence.workflow.Workflow;
@@ -59,9 +60,12 @@ final class POJOWorkflowImplementationFactory implements ReplayWorkflowFactory {
5960
private final Map<String, Functions.Func<SyncWorkflowDefinition>> workflowDefinitions =
6061
Collections.synchronizedMap(new HashMap<>());
6162

63+
private final Map<Class<?>, Functions.Func<?>> workflowImplementationFactories =
64+
Collections.synchronizedMap(new HashMap<>());
65+
6266
private final ExecutorService threadPool;
6367

64-
public POJOWorkflowImplementationFactory(
68+
POJOWorkflowImplementationFactory(
6569
DataConverter dataConverter,
6670
ExecutorService threadPool,
6771
Function<WorkflowInterceptor, WorkflowInterceptor> interceptorFactory) {
@@ -70,14 +74,19 @@ public POJOWorkflowImplementationFactory(
7074
this.interceptorFactory = Objects.requireNonNull(interceptorFactory);
7175
}
7276

73-
public void setWorkflowImplementationTypes(Class<?>[] workflowImplementationTypes) {
77+
void setWorkflowImplementationTypes(Class<?>[] workflowImplementationTypes) {
7478
workflowDefinitions.clear();
7579
for (Class<?> type : workflowImplementationTypes) {
7680
addWorkflowImplementationType(type);
7781
}
7882
}
7983

80-
public void addWorkflowImplementationType(Class<?> workflowImplementationClass) {
84+
<R> void addWorkflowImplementationFactory(Class<R> clazz, Functions.Func<R> factory) {
85+
workflowImplementationFactories.put(clazz, factory);
86+
addWorkflowImplementationType(clazz);
87+
}
88+
89+
private void addWorkflowImplementationType(Class<?> workflowImplementationClass) {
8190
TypeToken<?>.TypeSet interfaces =
8291
TypeToken.of(workflowImplementationClass).getTypes().interfaces();
8392
if (interfaces.isEmpty()) {
@@ -230,17 +239,22 @@ public byte[] execute(byte[] input) throws CancellationException, WorkflowExecut
230239

231240
private void newInstance() {
232241
if (workflow == null) {
233-
try {
234-
workflow = workflowImplementationClass.getDeclaredConstructor().newInstance();
235-
} catch (NoSuchMethodException
236-
| InstantiationException
237-
| IllegalAccessException
238-
| InvocationTargetException e) {
239-
// Error to fail decision as this can be fixed by a new deployment.
240-
throw new Error(
241-
"Failure instantiating workflow implementation class "
242-
+ workflowImplementationClass.getName(),
243-
e);
242+
Func<?> factory = workflowImplementationFactories.get(workflowImplementationClass);
243+
if (factory != null) {
244+
workflow = factory.apply();
245+
} else {
246+
try {
247+
workflow = workflowImplementationClass.getDeclaredConstructor().newInstance();
248+
} catch (NoSuchMethodException
249+
| InstantiationException
250+
| IllegalAccessException
251+
| InvocationTargetException e) {
252+
// Error to fail decision as this can be fixed by a new deployment.
253+
throw new Error(
254+
"Failure instantiating workflow implementation class "
255+
+ workflowImplementationClass.getName(),
256+
e);
257+
}
244258
}
245259
WorkflowInternal.registerQuery(workflow);
246260
}
@@ -289,7 +303,7 @@ public void processSignal(String signalName, byte[] input, long eventId) {
289303
}
290304
}
291305

292-
public static WorkflowExecutionException mapToWorkflowExecutionException(
306+
static WorkflowExecutionException mapToWorkflowExecutionException(
293307
Exception failure, DataConverter dataConverter) {
294308
failure = CheckedExceptionWrapper.unwrap(failure);
295309
// Only expected during unit tests.

src/main/java/com/uber/cadence/internal/sync/SyncWorkflowWorker.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.uber.cadence.internal.worker.SingleWorkerOptions;
2424
import com.uber.cadence.internal.worker.WorkflowWorker;
2525
import com.uber.cadence.serviceclient.IWorkflowService;
26+
import com.uber.cadence.workflow.Functions.Func;
2627
import com.uber.cadence.workflow.WorkflowInterceptor;
2728
import java.util.concurrent.SynchronousQueue;
2829
import java.util.concurrent.ThreadPoolExecutor;
@@ -63,6 +64,10 @@ public void setWorkflowImplementationTypes(Class<?>[] workflowImplementationType
6364
factory.setWorkflowImplementationTypes(workflowImplementationTypes);
6465
}
6566

67+
public <R> void addWorkflowImplementationFactory(Class<R> clazz, Func<R> factory) {
68+
this.factory.addWorkflowImplementationFactory(clazz, factory);
69+
}
70+
6671
public void start() {
6772
worker.start();
6873
}

src/main/java/com/uber/cadence/worker/Worker.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717

1818
package com.uber.cadence.worker;
1919

20+
import com.google.common.annotations.VisibleForTesting;
2021
import com.uber.cadence.WorkflowExecution;
2122
import com.uber.cadence.client.WorkflowClient;
2223
import com.uber.cadence.internal.sync.SyncActivityWorker;
2324
import com.uber.cadence.internal.sync.SyncWorkflowWorker;
2425
import com.uber.cadence.internal.worker.SingleWorkerOptions;
2526
import com.uber.cadence.serviceclient.IWorkflowService;
2627
import com.uber.cadence.serviceclient.WorkflowServiceTChannel;
28+
import com.uber.cadence.workflow.Functions.Func;
2729
import java.time.Duration;
2830
import java.util.Objects;
2931
import java.util.concurrent.TimeUnit;
@@ -175,6 +177,32 @@ public void registerWorkflowImplementationTypes(Class<?>... workflowImplementati
175177
workflowWorker.setWorkflowImplementationTypes(workflowImplementationClasses);
176178
}
177179

180+
/**
181+
* Configures a factory to use when an instance of a workflow implementation is created. The only
182+
* valid use for this method is unit testing, specifically to instantiate mocks that implement
183+
* child workflows. An example of mocking a child workflow:
184+
*
185+
* <pre><code>
186+
* worker.addWorkflowImplementationFactory(ChildWorkflow.class, () -> {
187+
* ChildWorkflow child = mock(ChildWorkflow.class);
188+
* when(child.workflow(anyString(), anyString())).thenReturn("result1");
189+
* return child;
190+
* });
191+
* </code></pre>
192+
*
193+
* <p>Unless mocking a workflow execution use {@link
194+
* #registerWorkflowImplementationTypes(Class[])}.
195+
*
196+
* @param workflowInterface Workflow interface that this factory implements
197+
* @param factory factory that when called creates a new instance of the workflow implementation
198+
* object.
199+
* @param <R> type of the workflow object to create.
200+
*/
201+
@VisibleForTesting
202+
public <R> void addWorkflowImplementationFactory(Class<R> workflowInterface, Func<R> factory) {
203+
workflowWorker.addWorkflowImplementationFactory(workflowInterface, factory);
204+
}
205+
178206
/**
179207
* Register activity implementation objects with a worker. Overwrites previously registered
180208
* objects. As activities are reentrant and stateless only one instance per activity type is

src/test/java/com/uber/cadence/internal/testing/WorkflowTestingTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import static org.junit.Assert.assertEquals;
2121
import static org.junit.Assert.assertTrue;
2222
import static org.junit.Assert.fail;
23+
import static org.mockito.Matchers.anyString;
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.when;
2326

2427
import com.uber.cadence.EventType;
2528
import com.uber.cadence.GetWorkflowExecutionHistoryRequest;
@@ -660,4 +663,27 @@ public void testChildSimulatedTimeout() {
660663
assertTrue(e.getCause() instanceof ChildWorkflowTimedOutException);
661664
}
662665
}
666+
667+
@Test
668+
public void testMockedChildSimulatedTimeout() {
669+
Worker worker = testEnvironment.newWorker(TASK_LIST);
670+
worker.registerWorkflowImplementationTypes(SimulatedTimeoutParentWorkflow.class);
671+
worker.addWorkflowImplementationFactory(
672+
ChildWorkflow.class,
673+
() -> {
674+
ChildWorkflow child = mock(ChildWorkflow.class);
675+
when(child.workflow(anyString(), anyString())).thenThrow(new SimulatedTimeoutException());
676+
return child;
677+
});
678+
worker.start();
679+
WorkflowClient client = testEnvironment.newWorkflowClient();
680+
WorkflowOptions options = new WorkflowOptions.Builder().setWorkflowId("parent1").build();
681+
ParentWorkflow workflow = client.newWorkflowStub(ParentWorkflow.class, options);
682+
try {
683+
workflow.workflow("input1");
684+
fail("unreacheable");
685+
} catch (WorkflowException e) {
686+
assertTrue(e.getCause() instanceof ChildWorkflowTimedOutException);
687+
}
688+
}
663689
}

0 commit comments

Comments
 (0)