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

temporalio / sdk-java / #400

06 Oct 2026 02:56AM UTC coverage: 68.324% (+0.007%) from 68.317%
#400

push

github

web-flow
Mark dev server APIs as stable (#3112)

7902 of 13718 branches covered (57.6%)

Branch coverage included in aggregate %.

32093 of 44819 relevant lines covered (71.61%)

0.72 hits per line

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

7.41
/temporal-testing/src/main/java/io/temporal/testing/TestWorkflowEnvironment.java
1
package io.temporal.testing;
2

3
import io.temporal.api.common.v1.WorkflowExecution;
4
import io.temporal.api.enums.v1.IndexedValueType;
5
import io.temporal.api.nexus.v1.Endpoint;
6
import io.temporal.client.ActivityClient;
7
import io.temporal.client.WorkflowClient;
8
import io.temporal.common.WorkflowExecutionHistory;
9
import io.temporal.serviceclient.OperatorServiceStubs;
10
import io.temporal.serviceclient.WorkflowServiceStubs;
11
import io.temporal.worker.Worker;
12
import io.temporal.worker.WorkerFactory;
13
import io.temporal.worker.WorkerOptions;
14
import java.io.Closeable;
15
import java.time.Duration;
16
import java.util.concurrent.TimeUnit;
17
import javax.annotation.Nonnull;
18
import javax.annotation.Nullable;
19

20
/**
21
 * TestWorkflowEnvironment provides workflow unit testing capabilities.
22
 *
23
 * <p>Testing the workflow code is hard as it might be potentially very long-running. The included
24
 * in-memory implementation of the Temporal service supports <b>an automatic time skipping</b>.
25
 * Anytime a workflow under the test as well as the unit test code are waiting on a timer (or sleep)
26
 * the internal service time is automatically advanced to the nearest time that unblocks one of the
27
 * waiting threads. This way a workflow that runs in production for months is unit tested in
28
 * milliseconds. Here is an example of a test that executes in a few milliseconds instead of over
29
 * two hours that are needed for the workflow to complete:
30
 *
31
 * <pre><code>
32
 *   public class SignaledWorkflowImpl implements SignaledWorkflow {
33
 *     private String signalInput;
34
 *
35
 *    {@literal @}Override
36
 *     public String workflow1(String input) {
37
 *       Workflow.sleep(Duration.ofHours(1));
38
 *       Workflow.await(() -&gt; signalInput != null);
39
 *       Workflow.sleep(Duration.ofHours(1));
40
 *       return signalInput + "-" + input;
41
 *     }
42
 *
43
 *    {@literal @}Override
44
 *     public void processSignal(String input) {
45
 *       signalInput = input;
46
 *    }
47
 *  }
48
 *
49
 * {@literal @}Test
50
 *  public void testSignal() throws ExecutionException, InterruptedException {
51
 *    TestWorkflowEnvironment testEnvironment = TestWorkflowEnvironment.newInstance();
52
 *
53
 *    // Creates a worker that polls tasks from the service owned by the testEnvironment.
54
 *    Worker worker = testEnvironment.newWorker(TASK_QUEUE);
55
 *    worker.registerWorkflowImplementationTypes(SignaledWorkflowImpl.class);
56
 *    worker.start();
57
 *
58
 *    // Creates a WorkflowClient that interacts with the server owned by the testEnvironment.
59
 *    WorkflowClient client = testEnvironment.getWorkflowClient();
60
 *    SignaledWorkflow workflow = client.newWorkflowStub(SignaledWorkflow.class);
61
 *
62
 *    // Starts a workflow execution
63
 *    CompletableFuture<String> result = WorkflowClient.execute(workflow::workflow1, "input1");
64
 *
65
 *    // The sleep forwards the service clock for 65 minutes without blocking.
66
 *    // This ensures that the signal is sent after the one hour sleep in the workflow code.
67
 *    testEnvironment.sleep(Duration.ofMinutes(65));
68
 *    workflow.processSignal("signalInput");
69
 *
70
 *    // Blocks until workflow is complete. Workflow sleep forwards clock for one hour and
71
 *    // this call returns almost immediately.
72
 *    assertEquals("signalInput-input1", result.get());
73
 *
74
 *    // Closes workers and releases in-memory service.
75
 *    testEnvironment.close();
76
 *  }
77
 *
78
 * </code></pre>
79
 */
80
public interface TestWorkflowEnvironment extends Closeable {
81

82
  /** Creates the environment with default options. */
83
  static TestWorkflowEnvironment newInstance() {
84
    return new TestWorkflowEnvironmentInternal(TestEnvironmentOptions.getDefaultInstance());
1 ✔
85
  }
86

87
  /** Creates the environment with supplied {@code options}. */
88
  static TestWorkflowEnvironment newInstance(TestEnvironmentOptions options) {
89
    return new TestWorkflowEnvironmentInternal(options);
1 ✔
90
  }
91

92
  /**
93
   * Starts a local Temporal dev server and returns an environment that owns it.
94
   *
95
   * <p>Unlike the in-memory test server, a local dev-server environment does not support time
96
   * skipping.
97
   *
98
   * <pre>{@code
99
   * try (TestWorkflowEnvironment environment = TestWorkflowEnvironment.startLocal()) {
100
   *   Worker worker = environment.newWorker("test-task-queue");
101
   *   // Register implementations and run workflows against the local dev server.
102
   * }
103
   * }</pre>
104
   */
105
  static TestWorkflowEnvironment startLocal() {
106
    return startLocal(
×
107
        TestEnvironmentOptions.getDefaultInstance(), TemporalDevServerOptions.getDefaultInstance());
×
108
  }
109

110
  /**
111
   * Starts a local Temporal dev server using the environment namespace and returns an environment
112
   * that owns it. Local dev-server environments do not support time skipping.
113
   */
114
  static TestWorkflowEnvironment startLocal(@Nullable TestEnvironmentOptions testOptions) {
115
    return startLocal(testOptions, TemporalDevServerOptions.getDefaultInstance());
×
116
  }
117

118
  /**
119
   * Starts a local Temporal dev server with the supplied server options. Local dev-server
120
   * environments do not support time skipping.
121
   */
122
  static TestWorkflowEnvironment startLocal(@Nonnull TemporalDevServerOptions serverOptions) {
123
    return startLocal(TestEnvironmentOptions.getDefaultInstance(), serverOptions);
×
124
  }
125

126
  /**
127
   * Starts a local Temporal dev server and returns an environment that owns it.
128
   *
129
   * <p>The namespace in {@code testOptions} is authoritative and is created by the dev server.
130
   * Local dev-server environments do not support time skipping.
131
   */
132
  static TestWorkflowEnvironment startLocal(
133
      @Nullable TestEnvironmentOptions testOptions,
134
      @Nonnull TemporalDevServerOptions serverOptions) {
135
    if (testOptions == null) {
×
136
      testOptions = TestEnvironmentOptions.getDefaultInstance();
×
137
    }
138
    TestEnvironmentOptions validated =
×
139
        TestEnvironmentOptions.newBuilder(testOptions).validateAndBuildWithDefaults();
×
140
    String namespace = validated.getWorkflowClientOptions().getNamespace();
×
141
    TemporalDevServer server = TemporalDevServer.start(namespace, serverOptions);
×
142
    try {
143
      TestEnvironmentOptions localOptions =
×
144
          TestEnvironmentOptions.newBuilder(validated)
×
145
              .setUseExternalService(true)
×
146
              .setUseTimeskipping(false)
×
147
              .setTarget(server.getTarget())
×
148
              .build();
×
149
      return new TestWorkflowEnvironmentInternal(localOptions, server);
×
150
    } catch (RuntimeException | Error failure) {
×
151
      try {
152
        server.close();
×
153
      } catch (RuntimeException | Error cleanupFailure) {
×
154
        failure.addSuppressed(cleanupFailure);
×
155
      }
×
156
      throw failure;
×
157
    }
158
  }
159

160
  /**
161
   * Creates a new Worker instance that is connected to the in-memory test Temporal service.
162
   *
163
   * @param taskQueue task queue to poll.
164
   */
165
  Worker newWorker(String taskQueue);
166

167
  /**
168
   * Creates a new Worker instance that is connected to the in-memory test Temporal service.
169
   *
170
   * @param taskQueue task queue to poll.
171
   */
172
  Worker newWorker(String taskQueue, WorkerOptions options);
173

174
  /** Creates a WorkflowClient that is connected to the in-memory test Temporal service. */
175
  WorkflowClient getWorkflowClient();
176

177
  /** Creates an ActivityClient that is connected to the in-memory test Temporal service. */
178
  ActivityClient getActivityClient();
179

180
  /**
181
   * This time might not be equal to {@link System#currentTimeMillis()} due to time skipping.
182
   *
183
   * @return the current in-memory test Temporal service time in milliseconds or {@link
184
   *     System#currentTimeMillis()} if an external service without time skipping support is used
185
   */
186
  long currentTimeMillis();
187

188
  /**
189
   * Wait until internal test Temporal service time passes the specified duration. This call also
190
   * indicates that workflow time might jump forward (if none of the activities are running) up to
191
   * the specified duration.
192
   *
193
   * <p>This method falls back to {@link Thread#sleep(long)} if an external service without time
194
   * skipping support is used
195
   */
196
  void sleep(Duration duration);
197

198
  /**
199
   * Registers a callback to run after the specified delay according to the test Temporal service
200
   * internal clock.
201
   */
202
  void registerDelayedCallback(Duration delay, Runnable r);
203

204
  /**
205
   * Register a Search Attribute with the server.
206
   *
207
   * @param name Search Attribute name
208
   * @param type Search Attribute type to be used for an elastic search index
209
   * @return {@code true} if the search attribute was registered, false if it was registered already
210
   * @see <a
211
   *     href="https://docs.temporal.io/self-hosted-guide/visibility#create-custom-search-attributes">
212
   *     How to create custom Search Attributes</a>
213
   */
214
  boolean registerSearchAttribute(String name, IndexedValueType type);
215

216
  /**
217
   * Register a Nexus Endpoint with the server.
218
   *
219
   * @param name Nexus Endpoint name
220
   * @param taskQueue Task Queue to be used for the endpoint
221
   * @return Endpoint object
222
   */
223
  Endpoint createNexusEndpoint(String name, String taskQueue);
224

225
  /**
226
   * Delete a Nexus Endpoint on the server.
227
   *
228
   * @param endpoint current endpoint to be deleted
229
   */
230
  void deleteNexusEndpoint(Endpoint endpoint);
231

232
  /**
233
   * @return the in-memory test Temporal service that is owned by this.
234
   * @deprecated use {{@link #getWorkflowServiceStubs()}
235
   */
236
  @Deprecated
237
  WorkflowServiceStubs getWorkflowService();
238

239
  /**
240
   * @return {@link WorkflowServiceStubs} connected to the test server (in-memory or external)
241
   */
242
  WorkflowServiceStubs getWorkflowServiceStubs();
243

244
  /**
245
   * @return {@link io.temporal.serviceclient.OperatorServiceStubs} connected to the test server
246
   */
247
  OperatorServiceStubs getOperatorServiceStubs();
248

249
  String getNamespace();
250

251
  /**
252
   * Currently prints histories of all workflow instances stored in the service. This is useful
253
   * information to print in the case of a unit test failure. A convenient way to achieve this is to
254
   * add the following Rule to a unit test:
255
   *
256
   * <pre><code>
257
   *  {@literal @}Rule
258
   *   public TestWatcher watchman =
259
   *       new TestWatcher() {
260
   *        {@literal @}Override
261
   *         protected void failed(Throwable e, Description description) {
262
   *           System.err.println(testEnvironment.getDiagnostics());
263
   *           testEnvironment.close();
264
   *         }
265
   *       };
266
   * </code></pre>
267
   *
268
   * @return the diagnostic data about the internal service state.
269
   */
270
  String getDiagnostics();
271

272
  /**
273
   * @param execution identifies the workflowId and runId (optionally) to reach the history for
274
   * @return history of the execution
275
   * @deprecated use {@link WorkflowClient#fetchHistory(String, String)}
276
   */
277
  @Deprecated
278
  WorkflowExecutionHistory getWorkflowExecutionHistory(@Nonnull WorkflowExecution execution);
279

280
  /** Calls {@link #shutdownNow()} and {@link #awaitTermination(long, TimeUnit)}. */
281
  @Override
282
  void close();
283

284
  WorkerFactory getWorkerFactory();
285

286
  /** Start all workers created by this factory. */
287
  void start();
288

289
  /** Was {@link #start()} called? */
290
  boolean isStarted();
291

292
  /** Was {@link #shutdownNow()} or {@link #shutdown()} called? */
293
  boolean isShutdown();
294

295
  /** Are all tasks done after {@link #shutdownNow()} or {@link #shutdown()}? */
296
  boolean isTerminated();
297

298
  /**
299
   * Initiates Test Service shutdown. This method is temporarily exposed to solve long poll thread
300
   * shutdown for {@code
301
   * io.temporal.workflow.interceptorsTests.InterceptorExceptionTests#testExceptionOnStart()}. See
302
   * issue: https://github.com/temporalio/sdk-java/issues/608
303
   */
304
  @Deprecated
305
  void shutdownTestService();
306

307
  /**
308
   * Initiates an orderly shutdown in which polls are stopped and already received workflow and
309
   * activity tasks are executed. After the shutdown calls to {@link
310
   * io.temporal.activity.ActivityExecutionContext#heartbeat(Object)} start throwing {@link
311
   * io.temporal.client.ActivityWorkerShutdownException}. Invocation has no additional effect if
312
   * already shut down. This method does not wait for previously received tasks to complete
313
   * execution. Use {@link #awaitTermination(long, TimeUnit)} to do that.
314
   */
315
  void shutdown();
316

317
  /**
318
   * Initiates an orderly shutdown in which polls are stopped and already received workflow and
319
   * activity tasks are attempted to be stopped. This implementation cancels tasks via
320
   * Thread.interrupt(), so any task that fails to respond to interrupts may never terminate. Also,
321
   * after the shutdownNow calls to {@link
322
   * io.temporal.activity.ActivityExecutionContext#heartbeat(Object)} start throwing {@link
323
   * io.temporal.client.ActivityWorkerShutdownException}. Invocation has no additional effect if
324
   * already shut down. This method does not wait for previously received tasks to complete
325
   * execution. Use {@link #awaitTermination(long, TimeUnit)} to do that.
326
   */
327
  void shutdownNow();
328

329
  /**
330
   * Blocks until all tasks have completed execution after a shutdown request, or the timeout
331
   * occurs, or the current thread is interrupted, whichever happens first.
332
   */
333
  void awaitTermination(long timeout, TimeUnit unit);
334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc