• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

temporalio / sdk-java / #175

pending completion
#175

push

github-actions

web-flow
Worker / Build Id versioning (#1786)

Implement new worker build id based versioning feature

236 of 236 new or added lines in 24 files covered. (100.0%)

18343 of 23697 relevant lines covered (77.41%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.89
/temporal-testing/src/main/java/io/temporal/testing/TestWorkflowRule.java
1
/*
2
 * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3
 *
4
 * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
 *
6
 * Modifications copyright (C) 2017 Uber Technologies, Inc.
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this material except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *   http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

21
package io.temporal.testing;
22

23
import com.uber.m3.tally.Scope;
24
import io.temporal.api.common.v1.WorkflowExecution;
25
import io.temporal.api.enums.v1.IndexedValueType;
26
import io.temporal.api.history.v1.History;
27
import io.temporal.api.workflowservice.v1.WorkflowServiceGrpc;
28
import io.temporal.client.WorkflowClient;
29
import io.temporal.client.WorkflowClientOptions;
30
import io.temporal.client.WorkflowOptions;
31
import io.temporal.client.WorkflowStub;
32
import io.temporal.common.SearchAttributeKey;
33
import io.temporal.common.interceptors.WorkerInterceptor;
34
import io.temporal.internal.common.env.DebugModeUtils;
35
import io.temporal.internal.docker.RegisterTestNamespace;
36
import io.temporal.serviceclient.WorkflowServiceStubs;
37
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
38
import io.temporal.worker.Worker;
39
import io.temporal.worker.WorkerFactoryOptions;
40
import io.temporal.worker.WorkerOptions;
41
import io.temporal.worker.WorkflowImplementationOptions;
42
import java.time.Instant;
43
import java.util.HashMap;
44
import java.util.Map;
45
import java.util.UUID;
46
import javax.annotation.Nonnull;
47
import javax.annotation.Nullable;
48
import org.junit.Test;
49
import org.junit.rules.TestRule;
50
import org.junit.rules.TestWatcher;
51
import org.junit.rules.Timeout;
52
import org.junit.runner.Description;
53
import org.junit.runners.model.Statement;
54

55
/**
56
 * JUnit4
57
 *
58
 * <p>Test rule that sets up test environment, simplifying workflow worker creation and shutdown.
59
 * Can be used with both in-memory and standalone temporal service. (see {@link
60
 * Builder#setUseExternalService(boolean)} and {@link Builder#setTarget(String)}})
61
 *
62
 * <p>Example of usage:
63
 *
64
 * <pre><code>
65
 *   public class MyTest {
66
 *
67
 *  {@literal @}Rule
68
 *   public TestWorkflowRule workflowRule =
69
 *       TestWorkflowRule.newBuilder()
70
 *           .setWorkflowTypes(TestWorkflowImpl.class)
71
 *           .setActivityImplementations(new TestActivities())
72
 *           .build();
73
 *
74
 *  {@literal @}Test
75
 *   public void testMyWorkflow() {
76
 *       TestWorkflow workflow = workflowRule.getWorkflowClient().newWorkflowStub(
77
 *                 TestWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(workflowRule.getTaskQueue()).build());
78
 *       ...
79
 *   }
80
 * </code></pre>
81
 */
82
public class TestWorkflowRule implements TestRule {
83

84
  private final String namespace;
85
  private final boolean useExternalService;
86
  private final boolean doNotStart;
87
  @Nullable private final Timeout globalTimeout;
88

89
  private final Class<?>[] workflowTypes;
90
  private final Object[] activityImplementations;
91
  private final WorkflowServiceStubsOptions serviceStubsOptions;
92
  private final WorkflowClientOptions clientOptions;
93
  private final WorkerFactoryOptions workerFactoryOptions;
94
  private final WorkflowImplementationOptions workflowImplementationOptions;
95
  private final WorkerOptions workerOptions;
96
  private final String target;
97
  private final boolean useTimeskipping;
98
  private final Scope metricsScope;
99

100
  @Nonnull private final Map<String, IndexedValueType> searchAttributes;
101

102
  private String taskQueue;
103
  private final TestWorkflowEnvironment testEnvironment;
104
  private final TestWatcher watchman =
1✔
105
      new TestWatcher() {
1✔
106
        @Override
107
        protected void failed(Throwable e, Description description) {
108
          System.err.println("WORKFLOW EXECUTION HISTORIES:\n" + testEnvironment.getDiagnostics());
×
109
        }
×
110
      };
111

112
  private TestWorkflowRule(Builder builder) {
1✔
113
    this.doNotStart = builder.doNotStart;
1✔
114
    this.useExternalService = builder.useExternalService;
1✔
115
    this.namespace =
1✔
116
        (builder.namespace == null) ? RegisterTestNamespace.NAMESPACE : builder.namespace;
1✔
117
    this.workflowTypes = (builder.workflowTypes == null) ? new Class[0] : builder.workflowTypes;
1✔
118
    this.activityImplementations =
1✔
119
        (builder.activityImplementations == null) ? new Object[0] : builder.activityImplementations;
1✔
120
    this.serviceStubsOptions =
1✔
121
        (builder.workflowServiceStubsOptions == null)
1✔
122
            ? WorkflowServiceStubsOptions.newBuilder().build()
1✔
123
            : builder.workflowServiceStubsOptions;
1✔
124
    this.clientOptions =
1✔
125
        (builder.workflowClientOptions == null)
1✔
126
            ? WorkflowClientOptions.newBuilder().setNamespace(namespace).build()
1✔
127
            : builder.workflowClientOptions.toBuilder().setNamespace(namespace).build();
1✔
128
    this.workerOptions =
1✔
129
        (builder.workerOptions == null)
1✔
130
            ? WorkerOptions.newBuilder().build()
1✔
131
            : builder.workerOptions;
1✔
132
    this.workerFactoryOptions =
1✔
133
        (builder.workerFactoryOptions == null)
1✔
134
            ? WorkerFactoryOptions.newBuilder().build()
×
135
            : builder.workerFactoryOptions;
1✔
136
    this.workflowImplementationOptions =
1✔
137
        (builder.workflowImplementationOptions == null)
1✔
138
            ? WorkflowImplementationOptions.newBuilder().build()
1✔
139
            : builder.workflowImplementationOptions;
1✔
140
    this.globalTimeout =
1✔
141
        !DebugModeUtils.isTemporalDebugModeOn() && builder.testTimeoutSeconds != 0
1✔
142
            ? Timeout.seconds(builder.testTimeoutSeconds)
×
143
            : null;
1✔
144

145
    this.target = builder.target;
1✔
146
    this.useTimeskipping = builder.useTimeskipping;
1✔
147
    this.metricsScope = builder.metricsScope;
1✔
148
    this.searchAttributes = builder.searchAttributes;
1✔
149

150
    this.testEnvironment =
1✔
151
        TestWorkflowEnvironment.newInstance(createTestEnvOptions(builder.initialTimeMillis));
1✔
152
  }
1✔
153

154
  protected TestEnvironmentOptions createTestEnvOptions(long initialTimeMillis) {
155
    return TestEnvironmentOptions.newBuilder()
1✔
156
        .setWorkflowServiceStubsOptions(serviceStubsOptions)
1✔
157
        .setWorkflowClientOptions(clientOptions)
1✔
158
        .setWorkerFactoryOptions(workerFactoryOptions)
1✔
159
        .setUseExternalService(useExternalService)
1✔
160
        .setUseTimeskipping(useTimeskipping)
1✔
161
        .setTarget(target)
1✔
162
        .setInitialTimeMillis(initialTimeMillis)
1✔
163
        .setMetricsScope(metricsScope)
1✔
164
        .setSearchAttributes(searchAttributes)
1✔
165
        .build();
1✔
166
  }
167

168
  public static Builder newBuilder() {
169
    return new Builder();
1✔
170
  }
171

172
  public static class Builder {
173

174
    private String namespace;
175
    private String target;
176
    private boolean useExternalService;
177
    private boolean doNotStart;
178
    private long initialTimeMillis;
179
    // Default to TestEnvironmentOptions isUseTimeskipping
180
    private boolean useTimeskipping =
1✔
181
        TestEnvironmentOptions.getDefaultInstance().isUseTimeskipping();
1✔
182

183
    private Class<?>[] workflowTypes;
184
    private Object[] activityImplementations;
185
    private WorkflowServiceStubsOptions workflowServiceStubsOptions;
186
    private WorkflowClientOptions workflowClientOptions;
187
    private WorkerFactoryOptions workerFactoryOptions;
188
    private WorkflowImplementationOptions workflowImplementationOptions;
189
    private WorkerOptions workerOptions;
190
    private long testTimeoutSeconds;
191
    @Nonnull private final Map<String, IndexedValueType> searchAttributes = new HashMap<>();
1✔
192
    private Scope metricsScope;
193

194
    protected Builder() {}
1✔
195

196
    public Builder setWorkerOptions(WorkerOptions options) {
197
      this.workerOptions = options;
1✔
198
      return this;
1✔
199
    }
200

201
    public void setWorkflowServiceStubsOptions(
202
        WorkflowServiceStubsOptions workflowServiceStubsOptions) {
203
      this.workflowServiceStubsOptions = workflowServiceStubsOptions;
1✔
204
    }
1✔
205

206
    /**
207
     * Override {@link WorkflowClientOptions} for test environment. If set, takes precedence over
208
     * {@link #setNamespace(String) namespace}.
209
     */
210
    public Builder setWorkflowClientOptions(WorkflowClientOptions workflowClientOptions) {
211
      this.workflowClientOptions = workflowClientOptions;
1✔
212
      return this;
1✔
213
    }
214

215
    public Builder setWorkerFactoryOptions(WorkerFactoryOptions options) {
216
      this.workerFactoryOptions = options;
1✔
217
      return this;
1✔
218
    }
219

220
    public Builder setNamespace(String namespace) {
221
      this.namespace = namespace;
×
222
      return this;
×
223
    }
224

225
    public Builder setWorkflowTypes(Class<?>... workflowTypes) {
226
      this.workflowTypes = workflowTypes;
1✔
227
      return this;
1✔
228
    }
229

230
    public Builder setWorkflowTypes(
231
        WorkflowImplementationOptions implementationOptions, Class<?>... workflowTypes) {
232
      this.workflowImplementationOptions = implementationOptions;
1✔
233
      this.workflowTypes = workflowTypes;
1✔
234
      return this;
1✔
235
    }
236

237
    public Builder setActivityImplementations(Object... activityImplementations) {
238
      this.activityImplementations = activityImplementations;
1✔
239
      return this;
1✔
240
    }
241

242
    /**
243
     * Switches between in-memory and external temporal service implementations.
244
     *
245
     * @param useExternalService use external service if true.
246
     *     <p>Default is false.
247
     */
248
    public Builder setUseExternalService(boolean useExternalService) {
249
      this.useExternalService = useExternalService;
×
250
      return this;
×
251
    }
252

253
    /**
254
     * Sets TestEnvironmentOptions.setUseTimeskippings. If true, no actual wall-clock time will pass
255
     * when a workflow sleeps or sets a timer.
256
     *
257
     * <p>Default is true
258
     */
259
    public Builder setUseTimeskipping(boolean useTimeskipping) {
260
      this.useTimeskipping = useTimeskipping;
1✔
261
      return this;
1✔
262
    }
263

264
    /**
265
     * Optional parameter that defines an endpoint which will be used for the communication with
266
     * standalone temporal service. Has no effect if {@link #setUseExternalService(boolean)} is set
267
     * to false.
268
     *
269
     * <p>Default is to use 127.0.0.1:7233
270
     */
271
    public Builder setTarget(String target) {
272
      this.target = target;
×
273
      return this;
×
274
    }
275

276
    /**
277
     * @deprecated Temporal test rule shouldn't be responsible for enforcing test timeouts. Use
278
     *     toolchain of your test framework to enforce timeouts.
279
     */
280
    @Deprecated
281
    public Builder setTestTimeoutSeconds(long testTimeoutSeconds) {
282
      this.testTimeoutSeconds = testTimeoutSeconds;
×
283
      return this;
×
284
    }
285

286
    /**
287
     * Set the initial time for the workflow virtual clock, milliseconds since epoch.
288
     *
289
     * <p>Default is current time
290
     */
291
    public Builder setInitialTimeMillis(long initialTimeMillis) {
292
      this.initialTimeMillis = initialTimeMillis;
1✔
293
      return this;
1✔
294
    }
295

296
    /**
297
     * Set the initial time for the workflow virtual clock.
298
     *
299
     * <p>Default is current time
300
     */
301
    public Builder setInitialTime(Instant initialTime) {
302
      this.initialTimeMillis = initialTime.toEpochMilli();
×
303
      return this;
×
304
    }
305

306
    /**
307
     * When set to true the {@link TestWorkflowEnvironment#start()} is not called by the rule before
308
     * executing the test. This to support tests that register activities and workflows with workers
309
     * directly instead of using only {@link TestWorkflowRule.Builder}.
310
     */
311
    public Builder setDoNotStart(boolean doNotStart) {
312
      this.doNotStart = doNotStart;
1✔
313
      return this;
1✔
314
    }
315

316
    /**
317
     * Add a search attribute to be registered on the Temporal Server.
318
     *
319
     * @param name name of the search attribute
320
     * @param type search attribute type
321
     * @return {@code this}
322
     * @see <a
323
     *     href="https://docs.temporal.io/docs/tctl/how-to-add-a-custom-search-attribute-to-a-cluster-using-tctl">Add
324
     *     a Custom Search Attribute Using tctl</a>
325
     */
326
    public Builder registerSearchAttribute(String name, IndexedValueType type) {
327
      this.searchAttributes.put(name, type);
1✔
328
      return this;
1✔
329
    }
330

331
    /**
332
     * Add a search attribute to be registered on the Temporal Server.
333
     *
334
     * @param key key to register
335
     * @return {@code this}
336
     * @see <a
337
     *     href="https://docs.temporal.io/docs/tctl/how-to-add-a-custom-search-attribute-to-a-cluster-using-tctl">Add
338
     *     a Custom Search Attribute Using tctl</a>
339
     */
340
    public Builder registerSearchAttribute(SearchAttributeKey<?> key) {
341
      return this.registerSearchAttribute(key.getName(), key.getValueType());
1✔
342
    }
343

344
    /**
345
     * Sets the scope to be used for metrics reporting. Optional. Default is to not report metrics.
346
     *
347
     * <p>Note: Don't mock {@link Scope} in tests! If you need to verify the metrics behavior,
348
     * create a real Scope and mock, stub or spy a reporter instance:<br>
349
     *
350
     * <pre>{@code
351
     * StatsReporter reporter = mock(StatsReporter.class);
352
     * Scope metricsScope =
353
     *     new RootScopeBuilder()
354
     *         .reporter(reporter)
355
     *         .reportEvery(com.uber.m3.util.Duration.ofMillis(10));
356
     * }</pre>
357
     *
358
     * @param metricsScope the scope to be used for metrics reporting.
359
     * @return {@code this}
360
     */
361
    public Builder setMetricsScope(Scope metricsScope) {
362
      this.metricsScope = metricsScope;
1✔
363
      return this;
1✔
364
    }
365

366
    public TestWorkflowRule build() {
367
      return new TestWorkflowRule(this);
1✔
368
    }
369
  }
370

371
  @Override
372
  public Statement apply(Statement base, Description description) {
373
    taskQueue = init(description);
1✔
374
    Statement testWorkflowStatement =
1✔
375
        new Statement() {
1✔
376
          @Override
377
          public void evaluate() throws Throwable {
378
            start();
1✔
379
            base.evaluate();
1✔
380
            shutdown();
1✔
381
          }
1✔
382
        };
383

384
    Test annotation = description.getAnnotation(Test.class);
1✔
385
    boolean timeoutIsOverriddenOnTestAnnotation = annotation != null && annotation.timeout() > 0;
1✔
386

387
    if (globalTimeout != null && !timeoutIsOverriddenOnTestAnnotation) {
1✔
388
      testWorkflowStatement = globalTimeout.apply(testWorkflowStatement, description);
×
389
    }
390

391
    return watchman.apply(testWorkflowStatement, description);
1✔
392
  }
393

394
  private String init(Description description) {
395
    String testMethod = description.getMethodName();
1✔
396
    String taskQueue = "WorkflowTest-" + testMethod + "-" + UUID.randomUUID();
1✔
397
    Worker worker = testEnvironment.newWorker(taskQueue, workerOptions);
1✔
398
    worker.registerWorkflowImplementationTypes(workflowImplementationOptions, workflowTypes);
1✔
399
    worker.registerActivitiesImplementations(activityImplementations);
1✔
400
    return taskQueue;
1✔
401
  }
402

403
  private void start() {
404
    if (!doNotStart) {
1✔
405
      testEnvironment.start();
1✔
406
    }
407
  }
1✔
408

409
  protected void shutdown() {
410
    testEnvironment.close();
1✔
411
  }
1✔
412

413
  /**
414
   * See {@link Builder#setUseExternalService(boolean)}
415
   *
416
   * @return true if the rule is using external temporal service.
417
   */
418
  public boolean isUseExternalService() {
419
    return useExternalService;
×
420
  }
421

422
  public TestWorkflowEnvironment getTestEnvironment() {
423
    return testEnvironment;
1✔
424
  }
425

426
  /**
427
   * @return name of the task queue that test worker is polling.
428
   */
429
  public String getTaskQueue() {
430
    return taskQueue;
1✔
431
  }
432

433
  /**
434
   * @return client to the Temporal service used to start and query workflows.
435
   */
436
  public WorkflowClient getWorkflowClient() {
437
    return testEnvironment.getWorkflowClient();
1✔
438
  }
439

440
  /**
441
   * @return stubs connected to the test server (in-memory or external)
442
   */
443
  public WorkflowServiceStubs getWorkflowServiceStubs() {
444
    return testEnvironment.getWorkflowServiceStubs();
1✔
445
  }
446

447
  /**
448
   * @return blockingStub
449
   */
450
  public WorkflowServiceGrpc.WorkflowServiceBlockingStub blockingStub() {
451
    return getWorkflowServiceStubs().blockingStub();
×
452
  }
453

454
  /**
455
   * @return tracer.
456
   */
457
  public <T extends WorkerInterceptor> T getInterceptor(Class<T> type) {
458
    if (workerFactoryOptions.getWorkerInterceptors() != null) {
1✔
459
      for (WorkerInterceptor interceptor : workerFactoryOptions.getWorkerInterceptors()) {
1✔
460
        if (type.isInstance(interceptor)) {
1✔
461
          return type.cast(interceptor);
1✔
462
        }
463
      }
464
    }
465
    return null;
×
466
  }
467

468
  /**
469
   * @return workflow execution history
470
   * @deprecated use {@link WorkflowClient#fetchHistory(String, String)}. To obtain a WorkflowClient
471
   *     use {@link #getWorkflowClient()}
472
   */
473
  @Deprecated
474
  public History getHistory(@Nonnull WorkflowExecution execution) {
475
    return testEnvironment.getWorkflowExecutionHistory(execution).getHistory();
×
476
  }
477

478
  /**
479
   * @return name of the task queue that test worker is polling.
480
   * @deprecated use {@link WorkflowClient#fetchHistory(String, String)}. To obtain a WorkflowClient
481
   *     use {@link #getWorkflowClient()}
482
   */
483
  @Deprecated
484
  public History getWorkflowExecutionHistory(WorkflowExecution execution) {
485
    return testEnvironment.getWorkflowExecutionHistory(execution).getHistory();
×
486
  }
487

488
  /**
489
   * This worker listens to the default task queue which is obtainable via the {@link
490
   * #getTaskQueue()} method.
491
   *
492
   * @return the default worker created for each test method.
493
   */
494
  public Worker getWorker() {
495
    return testEnvironment.getWorkerFactory().getWorker(getTaskQueue());
1✔
496
  }
497

498
  public WorkerFactoryOptions getWorkerFactoryOptions() {
499
    return workerFactoryOptions;
×
500
  }
501

502
  public <T> T newWorkflowStub(Class<T> workflow) {
503
    return getWorkflowClient()
1✔
504
        .newWorkflowStub(workflow, newWorkflowOptionsForTaskQueue(getTaskQueue()));
1✔
505
  }
506

507
  public WorkflowStub newUntypedWorkflowStub(String workflow) {
508
    return getWorkflowClient()
1✔
509
        .newUntypedWorkflowStub(workflow, newWorkflowOptionsForTaskQueue(getTaskQueue()));
1✔
510
  }
511

512
  private static WorkflowOptions newWorkflowOptionsForTaskQueue(String taskQueue) {
513
    return WorkflowOptions.newBuilder().setTaskQueue(taskQueue).build();
1✔
514
  }
515
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc