X Tutup
Skip to content

Commit a615537

Browse files
committed
Wrote 4.2
1 parent 4f1bbe1 commit a615537

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed

Part 4 - Concurrency/2. Testing Rx.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,226 @@
1+
# Testing
12

3+
When designing any system, you also want to test it to guarantee its quality and that this quality does not regress as the system is modified throughout its lifetime. Ideally, you want to automate the process of testing it. Modern software is backed by thorough unit tests and Rx code should be no different.
4+
5+
Testing a synchronous piece of Rx code is as straight-forward as any unit test you are likely to find, using predefined sequences and [inspection](/Part 2 - Sequence Basics/3. Inspection.md). But what about asynchronous code? Consider testing the following piece of code:
6+
```java
7+
Observable.interval(1, TimeUnit.SECONDS)
8+
.take(5)
9+
```
10+
That is a sequence that takes 5 seconds to complete. That means every test that uses it will also take 5 seconds or more. That's not convenient at all if you have thousands of tests to run.
11+
12+
## TestScheduler
13+
14+
The piece of code above isn't just time consuming, it actually wastes all that time waiting for time to pass. If you could fast-forward the clock, that sequence would be evaluated almost instantly. Well, you can't fast-forward your system's clock, but Rx sees time through its schedulers. You can have a scheduler that virtualises time, called `TestScheduler`.
15+
16+
The `TestScheduler` does scheduling in the same way as the schedulers that we saw in the chapter about [Scheduling and threading](/Part 4 - Concurrency/1. Scheduling and threading.md). It schedules actions to be executed either immediately or in the future. The difference is that time is frozen and only progresses upon request.
17+
18+
### advanceTimeTo
19+
20+
As the name suggests, `advanceTimeTo` will execute all actions that are scheduled for up to a specific moment in time. That includes actions scheduled while the scheduler was being fast-forwarded, i.e. actions scheduled by other actions.
21+
22+
```java
23+
TestScheduler s = Schedulers.test();
24+
25+
s.createWorker().schedule(
26+
() -> System.out.println("Immediate"));
27+
s.createWorker().schedule(
28+
() -> System.out.println("20s"),
29+
20, TimeUnit.SECONDS);
30+
s.createWorker().schedule(
31+
() -> System.out.println("40s"),
32+
40, TimeUnit.SECONDS);
33+
34+
System.out.println("Advancing to 1ms");
35+
s.advanceTimeTo(1, TimeUnit.MILLISECONDS);
36+
System.out.println("Virtual time: " + s.now());
37+
38+
System.out.println("Advancing to 10s");
39+
s.advanceTimeTo(10, TimeUnit.SECONDS);
40+
System.out.println("Virtual time: " + s.now());
41+
42+
System.out.println("Advancing to 40s");
43+
s.advanceTimeTo(40, TimeUnit.SECONDS);
44+
System.out.println("Virtual time: " + s.now());
45+
```
46+
Output
47+
```
48+
Advancing to 1ms
49+
Immediate
50+
Virtual time: 1
51+
Advancing to 10s
52+
Virtual time: 10000
53+
Advancing to 40s
54+
20s
55+
40s
56+
Virtual time: 40000
57+
```
58+
59+
We scheduled 3 tasks: one to be executed immediately, and two to be executed in the future. Nothing happens until we advance time, including tasks scheduled immediately. When we advance time, tasks are executed from the queue, until a task that is not ready yet.
60+
61+
You can even set time to a previous moment than the one you are now. That isn't a useful feature as much as a source of bugs in your tests. Usually, `advanceTimeBy` will be closer to what you want to do.
62+
63+
### advanceTimeBy
64+
65+
`advanceTimeBy` advances time relative to the current moment in time. In every other regard, works like `advanceTimeTo`.
66+
67+
```java
68+
TestScheduler s = Schedulers.test();
69+
70+
s.createWorker().schedule(
71+
() -> System.out.println("Immediate"));
72+
s.createWorker().schedule(
73+
() -> System.out.println("20s"),
74+
20, TimeUnit.SECONDS);
75+
s.createWorker().schedule(
76+
() -> System.out.println("40s"),
77+
40, TimeUnit.SECONDS);
78+
79+
System.out.println("Advancing by 1ms");
80+
s.advanceTimeBy(1, TimeUnit.MILLISECONDS);
81+
System.out.println("Virtual time: " + s.now());
82+
83+
System.out.println("Advancing by 10s");
84+
s.advanceTimeBy(10, TimeUnit.SECONDS);
85+
System.out.println("Virtual time: " + s.now());
86+
87+
System.out.println("Advancing by 40s");
88+
s.advanceTimeBy(40, TimeUnit.SECONDS);
89+
System.out.println("Virtual time: " + s.now());
90+
```
91+
Output
92+
```
93+
Advancing by 1ms
94+
Immediate
95+
Virtual time: 1
96+
Advancing by 10s
97+
Virtual time: 10001
98+
Advancing by 40s
99+
20s
100+
40s
101+
Virtual time: 50001
102+
```
103+
104+
### triggerActions
105+
106+
`triggerActions` does not advance time. It only executes actions that were scheduled to be executed up to the present.
107+
108+
```java
109+
TestScheduler s = Schedulers.test();
110+
111+
s.createWorker().schedule(
112+
() -> System.out.println("Immediate"));
113+
s.createWorker().schedule(
114+
() -> System.out.println("20s"),
115+
20, TimeUnit.SECONDS);
116+
117+
s.triggerActions();
118+
System.out.println("Virtual time: " + s.now());
119+
```
120+
Output
121+
```
122+
Immediate
123+
Virtual time: 0
124+
```
125+
126+
### Scheduling collisions
127+
128+
There is nothing preventing actions from being scheduled for the same moment in time. When that happens, we have a scheduling collision. The order that two simultaneous tasks are executed is the same as the order in which they where scheduled.
129+
130+
```java
131+
TestScheduler s = Schedulers.test();
132+
133+
s.createWorker().schedule(
134+
() -> System.out.println("First"),
135+
20, TimeUnit.SECONDS);
136+
s.createWorker().schedule(
137+
() -> System.out.println("Second"),
138+
20, TimeUnit.SECONDS);
139+
s.createWorker().schedule(
140+
() -> System.out.println("Third"),
141+
20, TimeUnit.SECONDS);
142+
143+
s.advanceTimeTo(20, TimeUnit.SECONDS);
144+
```
145+
Output
146+
```
147+
First
148+
Second
149+
Third
150+
```
151+
152+
## Testing
153+
154+
Rx operators involving asynchronous, schedule those actions using a scheduler. If you take a look on all the operators in [Observable](http://reactivex.io/RxJava/javadoc/rx/Observable.html), you will see that such operators have overloads that take a scheduler. This is the way that you can supplement their real-time default schedulers for your `TestScheduler`.
155+
156+
Here is an example where we will test the output of `Observable.interval` against what we expect it to emit.
157+
158+
```java
159+
@Test
160+
public void test() {
161+
TestScheduler scheduler = new TestScheduler();
162+
List<Long> expected = Arrays.asList(0L, 1L, 2L, 3L, 4L);
163+
List<Long> result = new ArrayList<>();
164+
Observable
165+
.interval(1, TimeUnit.SECONDS, scheduler)
166+
.take(5)
167+
.subscribe(i -> result.add(i));
168+
assertTrue(result.isEmpty());
169+
scheduler.advanceTimeBy(5, TimeUnit.SECONDS);
170+
assertTrue(result.equals(expected));
171+
}
172+
```
173+
174+
This is useful for testing small, self-contained pieces of Rx code, such as custom operators. A complete system may be using schedulers on its own, thus defeating our virtual time. [Lee Campbell suggests](http://www.introtorx.com/Content/v1.0.10621.0/16_TestingRx.html#TestingRx) abstracting over Rx's scheduler factories (`Schedulers`), with a provider of our own. When in debug-mode, our custom scheduler factory will replace all schedulers with a `TestScheduler`, which we will then use to control time throughout our system.
175+
176+
177+
### TestSubscriber
178+
179+
In the test above, we manually collected the values emitted and compared them against what we expected. This process is common enough in tests that Rx comes packaged with `TestScubscriber`, which will do that for us. It collectes every notification received. With `TestSubscriber` our previous test becomes:
180+
181+
```java
182+
@Test
183+
public void test() {
184+
TestScheduler scheduler = new TestScheduler();
185+
TestSubscriber<Long> subscriber = new TestSubscriber<>();
186+
List<Long> expected = Arrays.asList(0L, 1L, 2L, 3L, 4L);
187+
Observable
188+
.interval(1, TimeUnit.SECONDS, scheduler)
189+
.take(5)
190+
.subscribe(subscriber);
191+
assertTrue(subscriber.getOnNextEvents().isEmpty());
192+
scheduler.advanceTimeBy(5, TimeUnit.SECONDS);
193+
subscriber.assertReceivedOnNext(expected);
194+
}
195+
```
196+
197+
A `TestSubscriber` collects more than just values and exposes them through the following methods:
198+
199+
```java
200+
java.lang.Thread getLastSeenThread()
201+
java.util.List<Notification<T>> getOnCompletedEvents()
202+
java.util.List<java.lang.Throwable> getOnErrorEvents()
203+
java.util.List<T> getOnNextEvents()
204+
```
205+
206+
There are two things to notice here. First is the `getLastSeenThread` method. A `TestSubscriber` checks on what thread it is notified and logs the most recent. That can be useful if, for example, you want to verify that an operation is/isn't executed on the GUI thread. Another interesting thing to notice is that there can be more than one termination event. That goes against how we defined our sequences in the begining of this guide. That is also the reason why the subscriber is capable of collecting multiple termination events: that would be a violation of the Rx contract and needs to be debugged.
207+
208+
`TestSubscriber` provides shorthands for a few basic assertions:
209+
```java
210+
void assertNoErrors()
211+
void assertReceivedOnNext(java.util.List<T> items)
212+
void assertTerminalEvent()
213+
void assertUnsubscribed()
214+
```
215+
216+
There is also a way to block execution until the observable that the `TestSubscriber` is subscribed to terminates.
217+
```java
218+
void awaitTerminalEvent()
219+
void awaitTerminalEvent(long timeout, java.util.concurrent.TimeUnit unit)
220+
void awaitTerminalEventAndUnsubscribeOnTimeout(long timeout, java.util.concurrent.TimeUnit unit)
221+
```
222+
223+
Awaiting with a timeout will cause an exception if the observable fails to complete on time.
2224

3225

4226
#### Continue reading

0 commit comments

Comments
 (0)
X Tutup