1+ package com .iluwatar .async .method .invocation ;
2+
3+ import org .junit .Test ;
4+ import org .mockito .ArgumentCaptor ;
5+ import org .mockito .Matchers ;
6+
7+ import java .util .Optional ;
8+ import java .util .concurrent .Callable ;
9+ import java .util .concurrent .ExecutionException ;
10+
11+ import static org .junit .Assert .assertEquals ;
12+ import static org .junit .Assert .assertFalse ;
13+ import static org .junit .Assert .assertNotNull ;
14+ import static org .junit .Assert .assertSame ;
15+ import static org .junit .Assert .assertTrue ;
16+ import static org .junit .Assert .fail ;
17+ import static org .mockito .Matchers .eq ;
18+ import static org .mockito .Mockito .mock ;
19+ import static org .mockito .Mockito .timeout ;
20+ import static org .mockito .Mockito .verify ;
21+ import static org .mockito .Mockito .verifyNoMoreInteractions ;
22+ import static org .mockito .Mockito .verifyZeroInteractions ;
23+ import static org .mockito .Mockito .when ;
24+ import static org .mockito .internal .verification .VerificationModeFactory .times ;
25+
26+ /**
27+ * Date: 12/6/15 - 10:49 AM
28+ *
29+ * @author Jeroen Meulemeester
30+ */
31+ public class ThreadAsyncExecutorTest {
32+
33+ /**
34+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)}
35+ */
36+ @ Test (timeout = 3000 )
37+ public void testSuccessfulTaskWithoutCallback () throws Exception {
38+ // Instantiate a new executor and start a new 'null' task ...
39+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
40+
41+ final Object result = new Object ();
42+ final Callable <Object > task = mock (Callable .class );
43+ when (task .call ()).thenReturn (result );
44+
45+ final AsyncResult <Object > asyncResult = executor .startProcess (task );
46+ assertNotNull (asyncResult );
47+ asyncResult .await (); // Prevent timing issues, and wait until the result is available
48+ assertTrue (asyncResult .isCompleted ());
49+
50+ // Our task should only execute once ...
51+ verify (task , times (1 )).call ();
52+
53+ // ... and the result should be exactly the same object
54+ assertSame (result , asyncResult .getValue ());
55+ }
56+
57+ /**
58+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)}
59+ */
60+ @ Test (timeout = 3000 )
61+ public void testSuccessfulTaskWithCallback () throws Exception {
62+ // Instantiate a new executor and start a new 'null' task ...
63+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
64+
65+ final Object result = new Object ();
66+ final Callable <Object > task = mock (Callable .class );
67+ when (task .call ()).thenReturn (result );
68+
69+ final AsyncCallback callback = mock (AsyncCallback .class );
70+ final AsyncResult <Object > asyncResult = executor .startProcess (task , callback );
71+ assertNotNull (asyncResult );
72+ asyncResult .await (); // Prevent timing issues, and wait until the result is available
73+ assertTrue (asyncResult .isCompleted ());
74+
75+ // Our task should only execute once ...
76+ verify (task , times (1 )).call ();
77+
78+ // ... same for the callback, we expect our object
79+ final ArgumentCaptor <Optional <Exception >> optionalCaptor = ArgumentCaptor .forClass ((Class ) Optional .class );
80+ verify (callback , times (1 )).onComplete (eq (result ), optionalCaptor .capture ());
81+
82+ final Optional <Exception > optionalException = optionalCaptor .getValue ();
83+ assertNotNull (optionalException );
84+ assertFalse (optionalException .isPresent ());
85+
86+ // ... and the result should be exactly the same object
87+ assertSame (result , asyncResult .getValue ());
88+ }
89+
90+ /**
91+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)}
92+ * when a task takes a while to execute
93+ */
94+ @ Test (timeout = 5000 )
95+ public void testLongRunningTaskWithoutCallback () throws Exception {
96+ // Instantiate a new executor and start a new 'null' task ...
97+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
98+
99+ final Object result = new Object ();
100+ final Callable <Object > task = mock (Callable .class );
101+ when (task .call ()).thenAnswer (i -> {
102+ Thread .sleep (1500 );
103+ return result ;
104+ });
105+
106+ final AsyncResult <Object > asyncResult = executor .startProcess (task );
107+ assertNotNull (asyncResult );
108+ assertFalse (asyncResult .isCompleted ());
109+
110+ try {
111+ asyncResult .getValue ();
112+ fail ("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task" );
113+ } catch (IllegalStateException e ) {
114+ assertNotNull (e .getMessage ());
115+ }
116+
117+ // Our task should only execute once, but it can take a while ...
118+ verify (task , timeout (3000 ).times (1 )).call ();
119+
120+ // Prevent timing issues, and wait until the result is available
121+ asyncResult .await ();
122+ assertTrue (asyncResult .isCompleted ());
123+ verifyNoMoreInteractions (task );
124+
125+ // ... and the result should be exactly the same object
126+ assertSame (result , asyncResult .getValue ());
127+ }
128+
129+ /**
130+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)}
131+ * when a task takes a while to execute
132+ */
133+ @ Test (timeout = 5000 )
134+ public void testLongRunningTaskWithCallback () throws Exception {
135+ // Instantiate a new executor and start a new 'null' task ...
136+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
137+
138+ final Object result = new Object ();
139+ final Callable <Object > task = mock (Callable .class );
140+ when (task .call ()).thenAnswer (i -> {
141+ Thread .sleep (1500 );
142+ return result ;
143+ });
144+
145+ final AsyncCallback <Object > callback = mock (AsyncCallback .class );
146+ final AsyncResult <Object > asyncResult = executor .startProcess (task , callback );
147+ assertNotNull (asyncResult );
148+ assertFalse (asyncResult .isCompleted ());
149+
150+ verifyZeroInteractions (callback );
151+
152+ try {
153+ asyncResult .getValue ();
154+ fail ("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task" );
155+ } catch (IllegalStateException e ) {
156+ assertNotNull (e .getMessage ());
157+ }
158+
159+ // Our task should only execute once, but it can take a while ...
160+ verify (task , timeout (3000 ).times (1 )).call ();
161+
162+ final ArgumentCaptor <Optional <Exception >> optionalCaptor = ArgumentCaptor .forClass ((Class ) Optional .class );
163+ verify (callback , timeout (3000 ).times (1 )).onComplete (eq (result ), optionalCaptor .capture ());
164+
165+ final Optional <Exception > optionalException = optionalCaptor .getValue ();
166+ assertNotNull (optionalException );
167+ assertFalse (optionalException .isPresent ());
168+
169+ // Prevent timing issues, and wait until the result is available
170+ asyncResult .await ();
171+ assertTrue (asyncResult .isCompleted ());
172+ verifyNoMoreInteractions (task , callback );
173+
174+ // ... and the result should be exactly the same object
175+ assertSame (result , asyncResult .getValue ());
176+ }
177+
178+ /**
179+ * Test used to verify the happy path of {@link ThreadAsyncExecutor#startProcess(Callable)}
180+ * when a task takes a while to execute, while waiting on the result using {@link ThreadAsyncExecutor#endProcess(AsyncResult)}
181+ */
182+ @ Test (timeout = 5000 )
183+ public void testEndProcess () throws Exception {
184+ // Instantiate a new executor and start a new 'null' task ...
185+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
186+
187+ final Object result = new Object ();
188+ final Callable <Object > task = mock (Callable .class );
189+ when (task .call ()).thenAnswer (i -> {
190+ Thread .sleep (1500 );
191+ return result ;
192+ });
193+
194+ final AsyncResult <Object > asyncResult = executor .startProcess (task );
195+ assertNotNull (asyncResult );
196+ assertFalse (asyncResult .isCompleted ());
197+
198+ try {
199+ asyncResult .getValue ();
200+ fail ("Expected IllegalStateException when calling AsyncResult#getValue on a non-completed task" );
201+ } catch (IllegalStateException e ) {
202+ assertNotNull (e .getMessage ());
203+ }
204+
205+ assertSame (result , executor .endProcess (asyncResult ));
206+ verify (task , times (1 )).call ();
207+ assertTrue (asyncResult .isCompleted ());
208+
209+ // Calling end process a second time while already finished should give the same result
210+ assertSame (result , executor .endProcess (asyncResult ));
211+ verifyNoMoreInteractions (task );
212+ }
213+
214+ /**
215+ * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable)}
216+ * when the callable is 'null'
217+ */
218+ @ Test (timeout = 3000 )
219+ public void testNullTask () throws Exception {
220+ // Instantiate a new executor and start a new 'null' task ...
221+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
222+ final AsyncResult <Object > asyncResult = executor .startProcess (null );
223+
224+ assertNotNull ("The AsyncResult should not be 'null', even though the task was 'null'." , asyncResult );
225+ asyncResult .await (); // Prevent timing issues, and wait until the result is available
226+ assertTrue (asyncResult .isCompleted ());
227+
228+ try {
229+ asyncResult .getValue ();
230+ fail ("Expected ExecutionException with NPE as cause" );
231+ } catch (final ExecutionException e ) {
232+ assertNotNull (e .getMessage ());
233+ assertNotNull (e .getCause ());
234+ assertEquals (NullPointerException .class , e .getCause ().getClass ());
235+ }
236+
237+ }
238+
239+ /**
240+ * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)}
241+ * when the callable is 'null', but the asynchronous callback is provided
242+ */
243+ @ Test (timeout = 3000 )
244+ public void testNullTaskWithCallback () throws Exception {
245+ // Instantiate a new executor and start a new 'null' task ...
246+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
247+ final AsyncCallback <Object > callback = mock (AsyncCallback .class );
248+ final AsyncResult <Object > asyncResult = executor .startProcess (null , callback );
249+
250+ assertNotNull ("The AsyncResult should not be 'null', even though the task was 'null'." , asyncResult );
251+ asyncResult .await (); // Prevent timing issues, and wait until the result is available
252+ assertTrue (asyncResult .isCompleted ());
253+
254+ final ArgumentCaptor <Optional <Exception >> optionalCaptor = ArgumentCaptor .forClass ((Class ) Optional .class );
255+ verify (callback , times (1 )).onComplete (Matchers .isNull (), optionalCaptor .capture ());
256+
257+ final Optional <Exception > optionalException = optionalCaptor .getValue ();
258+ assertNotNull (optionalException );
259+ assertTrue (optionalException .isPresent ());
260+
261+ final Exception exception = optionalException .get ();
262+ assertNotNull (exception );
263+ assertEquals (NullPointerException .class , exception .getClass ());
264+
265+ try {
266+ asyncResult .getValue ();
267+ fail ("Expected ExecutionException with NPE as cause" );
268+ } catch (final ExecutionException e ) {
269+ assertNotNull (e .getMessage ());
270+ assertNotNull (e .getCause ());
271+ assertEquals (NullPointerException .class , e .getCause ().getClass ());
272+ }
273+
274+ }
275+
276+ /**
277+ * Test used to verify the behaviour of {@link ThreadAsyncExecutor#startProcess(Callable, AsyncCallback)}
278+ * when both the callable and the asynchronous callback are 'null'
279+ */
280+ @ Test (timeout = 3000 )
281+ public void testNullTaskWithNullCallback () throws Exception {
282+ // Instantiate a new executor and start a new 'null' task ...
283+ final ThreadAsyncExecutor executor = new ThreadAsyncExecutor ();
284+ final AsyncResult <Object > asyncResult = executor .startProcess (null , null );
285+
286+ assertNotNull ("The AsyncResult should not be 'null', even though the task and callback were 'null'." , asyncResult );
287+ asyncResult .await (); // Prevent timing issues, and wait until the result is available
288+ assertTrue (asyncResult .isCompleted ());
289+
290+ try {
291+ asyncResult .getValue ();
292+ fail ("Expected ExecutionException with NPE as cause" );
293+ } catch (final ExecutionException e ) {
294+ assertNotNull (e .getMessage ());
295+ assertNotNull (e .getCause ());
296+ assertEquals (NullPointerException .class , e .getCause ().getClass ());
297+ }
298+
299+ }
300+
301+ }
0 commit comments