1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2 
3 using System;
4 using System.Reactive;
5 
6 namespace Microsoft.Reactive.Testing
7 {
8     /// <summary>
9     /// Base class to write unit tests for applications and libraries built using Reactive Extensions.
10     /// </summary>
11     public class ReactiveTest
12     {
13         /// <summary>
14         /// Default virtual time used for creation of observable sequences in <see cref="ReactiveTest"/>-based unit tests.
15         /// </summary>
16         public const long Created = 100;
17 
18         /// <summary>
19         /// Default virtual time used to subscribe to observable sequences in <see cref="ReactiveTest"/>-based unit tests.
20         /// </summary>
21         public const long Subscribed = 200;
22 
23         /// <summary>
24         /// Default virtual time used to dispose subscriptions in <see cref="ReactiveTest"/>-based unit tests.
25         /// </summary>
26         public const long Disposed = 1000;
27 
28         /// <summary>
29         /// Factory method for an OnNext notification record at a given time with a given value.
30         /// </summary>
31         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
32         /// <param name="ticks">Recorded virtual time the OnNext notification occurs.</param>
33         /// <param name="value">Recorded value stored in the OnNext notification.</param>
34         /// <returns>Recorded OnNext notification.</returns>
OnNext(long ticks, T value)35         public static Recorded<Notification<T>> OnNext<T>(long ticks, T value)
36         {
37             return new Recorded<Notification<T>>(ticks, Notification.CreateOnNext<T>(value));
38         }
39 
40         /// <summary>
41         /// Factory method for writing an assert that checks for an OnNext notification record at a given time, using the specified predicate to check the value.
42         /// </summary>
43         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
44         /// <param name="ticks">Recorded virtual time the OnNext notification occurs.</param>
45         /// <param name="predicate">Predicate function to check the OnNext notification value against an expected value.</param>
46         /// <returns>Recorded OnNext notification with a predicate to assert a given value.</returns>
47         /// <exception cref="ArgumentNullException"><paramref name="predicate"/> is null.</exception>
OnNext(long ticks, Func<T, bool> predicate)48         public static Recorded<Notification<T>> OnNext<T>(long ticks, Func<T, bool> predicate)
49         {
50             if (predicate == null)
51                 throw new ArgumentNullException("predicate");
52 
53             return new Recorded<Notification<T>>(ticks, new OnNextPredicate<T>(predicate));
54         }
55 
56         /// <summary>
57         /// Factory method for an OnCompleted notification record at a given time.
58         /// </summary>
59         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
60         /// <param name="ticks">Recorded virtual time the OnCompleted notification occurs.</param>
61         /// <returns>Recorded OnCompleted notification.</returns>
OnCompleted(long ticks)62         public static Recorded<Notification<T>> OnCompleted<T>(long ticks)
63         {
64             return new Recorded<Notification<T>>(ticks, Notification.CreateOnCompleted<T>());
65         }
66 
67         /// <summary>
68         /// Factory method for an OnCompleted notification record at a given time, using inference to determine the type of <typeparamref name="T"/>.
69         /// </summary>
70         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
71         /// <param name="ticks">Recorded virtual time the OnCompleted notification occurs.</param>
72         /// <param name="witness">Object solely used to infer the type of the <typeparamref name="T"/> type parameter. This parameter is typically used when creating a sequence of anonymously typed elements.</param>
73         /// <returns>Recorded OnCompleted notification.</returns>
OnCompleted(long ticks, T witness)74         public static Recorded<Notification<T>> OnCompleted<T>(long ticks, T witness)
75         {
76             return new Recorded<Notification<T>>(ticks, Notification.CreateOnCompleted<T>());
77         }
78 
79         /// <summary>
80         /// Factory method for an OnError notification record at a given time with a given error.
81         /// </summary>
82         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
83         /// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
84         /// <param name="exception">Recorded exception stored in the OnError notification.</param>
85         /// <returns>Recorded OnError notification.</returns>
86         /// <exception cref="ArgumentNullException"><paramref name="exception"/> is null.</exception>
OnError(long ticks, Exception exception)87         public static Recorded<Notification<T>> OnError<T>(long ticks, Exception exception)
88         {
89             if (exception == null)
90                 throw new ArgumentNullException("exception");
91 
92             return new Recorded<Notification<T>>(ticks, Notification.CreateOnError<T>(exception));
93         }
94 
95         /// <summary>
96         /// Factory method for writing an assert that checks for an OnError notification record at a given time, using the specified predicate to check the exception.
97         /// </summary>
98         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
99         /// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
100         /// <param name="predicate">Predicate function to check the OnError notification value against an expected exception.</param>
101         /// <returns>Recorded OnError notification with a predicate to assert a given exception.</returns>
102         /// <exception cref="ArgumentNullException"><paramref name="predicate"/> is null.</exception>
OnError(long ticks, Func<Exception, bool> predicate)103         public static Recorded<Notification<T>> OnError<T>(long ticks, Func<Exception, bool> predicate)
104         {
105             if (predicate == null)
106                 throw new ArgumentNullException("predicate");
107 
108             return new Recorded<Notification<T>>(ticks, new OnErrorPredicate<T>(predicate));
109         }
110 
111         /// <summary>
112         /// Factory method for an OnError notification record at a given time with a given error, using inference to determine the type of <typeparamref name="T"/>.
113         /// </summary>
114         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
115         /// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
116         /// <param name="exception">Recorded exception stored in the OnError notification.</param>
117         /// <param name="witness">Object solely used to infer the type of the <typeparamref name="T"/> type parameter. This parameter is typically used when creating a sequence of anonymously typed elements.</param>
118         /// <returns>Recorded OnError notification.</returns>
119         /// <exception cref="ArgumentNullException"><paramref name="exception"/> is null.</exception>
OnError(long ticks, Exception exception, T witness)120         public static Recorded<Notification<T>> OnError<T>(long ticks, Exception exception, T witness)
121         {
122             if (exception == null)
123                 throw new ArgumentNullException("exception");
124 
125             return new Recorded<Notification<T>>(ticks, Notification.CreateOnError<T>(exception));
126         }
127 
128         /// <summary>
129         /// Factory method for writing an assert that checks for an OnError notification record at a given time, using the specified predicate to check the exception and inference to determine the type of <typeparamref name="T"/>.
130         /// </summary>
131         /// <typeparam name="T">The element type for the resulting notification object.</typeparam>
132         /// <param name="ticks">Recorded virtual time the OnError notification occurs.</param>
133         /// <param name="predicate">Predicate function to check the OnError notification value against an expected exception.</param>
134         /// <param name="witness">Object solely used to infer the type of the <typeparamref name="T"/> type parameter. This parameter is typically used when creating a sequence of anonymously typed elements.</param>
135         /// <returns>Recorded OnError notification with a predicate to assert a given exception.</returns>
136         /// <exception cref="ArgumentNullException"><paramref name="predicate"/> is null.</exception>
OnError(long ticks, Func<Exception, bool> predicate, T witness)137         public static Recorded<Notification<T>> OnError<T>(long ticks, Func<Exception, bool> predicate, T witness)
138         {
139             if (predicate == null)
140                 throw new ArgumentNullException("predicate");
141 
142             return new Recorded<Notification<T>>(ticks, new OnErrorPredicate<T>(predicate));
143         }
144 
145         /// <summary>
146         /// Factory method for a subscription record based on a given subscription and disposal time.
147         /// </summary>
148         /// <param name="start">Virtual time indicating when the subscription was created.</param>
149         /// <param name="end">Virtual time indicating when the subscription was disposed.</param>
150         /// <returns>Subscription object.</returns>
Subscribe(long start, long end)151         public static Subscription Subscribe(long start, long end)
152         {
153             return new Subscription(start, end);
154         }
155 
156         /// <summary>
157         /// Factory method for a subscription record based on a given subscription time.
158         /// </summary>
159         /// <param name="start">Virtual time indicating when the subscription was created.</param>
160         /// <returns>Subscription object.</returns>
Subscribe(long start)161         public static Subscription Subscribe(long start)
162         {
163             return new Subscription(start);
164         }
165 
166         #region Predicate-based notification assert helper classes
167 
168         class OnNextPredicate<T> : PredicateNotification<T>
169         {
170             private readonly Func<T, bool> _predicate;
171 
OnNextPredicate(Func<T, bool> predicate)172             public OnNextPredicate(Func<T, bool> predicate)
173             {
174                 _predicate = predicate;
175             }
176 
Equals(Notification<T> other)177             public override bool Equals(Notification<T> other)
178             {
179                 if (Object.ReferenceEquals(this, other))
180                     return true;
181                 if (Object.ReferenceEquals(other, null))
182                     return false;
183                 if (other.Kind != NotificationKind.OnNext)
184                     return false;
185 
186                 return _predicate(other.Value);
187             }
188         }
189 
190         class OnErrorPredicate<T> : PredicateNotification<T>
191         {
192             private readonly Func<Exception, bool> _predicate;
193 
OnErrorPredicate(Func<Exception, bool> predicate)194             public OnErrorPredicate(Func<Exception, bool> predicate)
195             {
196                 _predicate = predicate;
197             }
198 
Equals(Notification<T> other)199             public override bool Equals(Notification<T> other)
200             {
201                 if (Object.ReferenceEquals(this, other))
202                     return true;
203                 if (Object.ReferenceEquals(other, null))
204                     return false;
205                 if (other.Kind != NotificationKind.OnError)
206                     return false;
207 
208                 return _predicate(other.Exception);
209             }
210         }
211 
212         abstract class PredicateNotification<T> : Notification<T>
213         {
214             #region Non-implemented members (by design)
215 
216             public override T Value
217             {
218                 get { throw new NotSupportedException(); }
219             }
220 
221             public override bool HasValue
222             {
223                 get { throw new NotSupportedException(); }
224             }
225 
226             public override Exception Exception
227             {
228                 get { throw new NotSupportedException(); }
229             }
230 
231             public override NotificationKind Kind
232             {
233                 get { throw new NotSupportedException(); }
234             }
235 
Accept(IObserver<T> observer)236             public override void Accept(IObserver<T> observer)
237             {
238                 throw new NotSupportedException();
239             }
240 
Accept(IObserver<T, TResult> observer)241             public override TResult Accept<TResult>(IObserver<T, TResult> observer)
242             {
243                 throw new NotSupportedException();
244             }
245 
Accept(Action<T> onNext, Action<Exception> onError, Action onCompleted)246             public override void Accept(Action<T> onNext, Action<Exception> onError, Action onCompleted)
247             {
248                 throw new NotSupportedException();
249             }
250 
Accept(Func<T, TResult> onNext, Func<Exception, TResult> onError, Func<TResult> onCompleted)251             public override TResult Accept<TResult>(Func<T, TResult> onNext, Func<Exception, TResult> onError, Func<TResult> onCompleted)
252             {
253                 throw new NotSupportedException();
254             }
255 
256             #endregion
257         }
258 
259         #endregion
260     }
261 }
262