Video can be found here: https://www.youtube.com/watch?v=qOST2eCgo2I
Rx helps us solve many complex problems, such as combining different streams and reacting to events that involve timing aspects.
However, solving those problems with code is not really "done" unless you can validate and assure your results.
In this session you'll learn the Rx.NET testing utilities and patterns that makes testing Rx code not only easy but also a lot of fun
16. Filtering Projection Partitioning Joins Grouping Set
Element Generation Quantifiers Aggregation Error HandlingTime and
Concurrency
Where
OfType
Select
SelectMany
Materialize
Skip
Take
TakeUntil
CombineLatest
Concat
join
GroupBy
GroupByUntil
Buffer
Distinct
DistinctUntilChanged
Timeout
TimeInterval
ElementAt
First
Single
Range
Repeat
Defer
All
Any
Contains
Sum
Average
Scan
Catch
OnErrorResumeNext
Using
Rx operators
16
18. 1. At least 3 characters
2. Don’t overflow server (0.5 sec delay)
3. Don’t send the same string again
4. Discard results if another search was requested
Reactive Search - RulesReactive Search - Rules
18
28. //
// Runs a timer on the default scheduler
//
IObservable TimeSpan
//
// Every operator that introduces concurrency
// has an overload with an IScheduler
//
IObservable T TimeSpan
IScheduler scheduler);
Parameterizing ConcurrencyParameterizing Concurrency
29
30. //
// runs the observer callbacks on the specified
// scheduler.
//
IObservable T ObserveOn<T>(IScheduler);
//
// runs the observer subscription and unsubsciption on
// the specified scheduler.
//
IObservable T SubscribeOn<T>(IScheduler)
Changing Execution ContextChanging Execution Context
31
33. Virtual Time
What is time?
Time can be a anything that is sequential and comparable
34
“Time is the indefinite continued progress of existence and events ... Time
is a component quantity of various measurements used
to sequence events, to compare the duration of events or the intervals
between them…”
https://en.wikipedia.org/wiki/Time
34. Virtual Time Scheduler
35
public abstract class VirtualTimeSchedulerBase<TAbsolute, TRelative> : IScheduler,
IServiceProvider, IStopwatchProvider
where TAbsolute : IComparable<TAbsolute>
{
public TAbsolute Clock { get; protected set;}
public void Start()
public void Stop()
public void AdvanceTo(TAbsolute time)
public void AdvanceBy(TRelative time)
...
}
public class TestScheduler : VirtualTimeScheduler<long, long>
{
...
}
36. 1. At least 3 characters
One letter word, No Search performed
2. Don’t overflow server (0.5 sec delay)
2 words typed, 0.2 sec gap, search only the last
3. Don’t send the same string again
2 words, 1 sec gap, same value, search only first
4. Discard results if another search was requested
2 words, 1 sec gap, slow first search, fast last search,
last results shown
Reactive Search - RulesReactive Search – Rules Tests
37
37. Questions and Answers
Q: How can we test the code without a real user interaction?
Q: How can we test the code without a real server?
Q: How can we test the code deterministically without a real asynchronicity and
concurrency?
Q: How can we test the code without REALLY waiting for the time to pass?
38
A: Separation of concerns. Separate the logic from the view
A: Enable Dependency Injection and mock the service client
A: Leverage the Rx Schedulers and provide a Scheduler you can control via DI
A: Leverage the Rx TestScheduler which provides a virtualization of time
38. Separating the logic from the view
39
SearchView
(Presentation, Logic, State)
SearchView
(Presentation)
SearchViewModel
(Logic, State)
Before
After
39. Separating the logic from the view
40
<Window x:Class="TestableReactiveSearch.SearchView">
<DockPanel>
<TextBox x:Name="SearchBox"
Text="{Binding SearchTerm …}"
DockPanel.Dock="Top“/>
<ListBox x:Name="SearchResults"
ItemsSource="{Binding SearchResults}“/>
</DockPanel>
</Window>
public class SearchViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SearchViewModel()
{
// Rx query
}
public string SearchTerm { get { ... } set { ... } }
public IEnumerable<string> SearchResults { get { ... } set { ... } }
}
SearchView.xaml
SearchViewModel.cs
40. Separating the logic from the view – fixing the Rx query
41
public SearchViewModel()
{
var terms =
Observable.FromEventPattern<PropertyChangedEventArgs>(this, nameof(PropertyChanged))
.Where(e => e.EventArgs.PropertyName == nameof(SearchTerm))
.Select(_ => SearchTerm);
_subscription =
terms
.Where(txt => txt.Length >= 3)
.Throttle(TimeSpan.FromSeconds(0.5))
.DistinctUntilChanged()
.Select(txt => searchServiceClient.SearchAsync(txt))
.Switch()
.ObserveOnDispatcher()
.Subscribe(
results => SearchResults = results,
err => { Debug.WriteLine(err); },
() => { /* OnCompleted */ });
}
Same query as before
41. Injecting the Search Service client
42
public class SearchViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SearchViewModel()
{
// rest of rx query
}
...
}
42. Injecting the Search Service client
43
public class SearchViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SearchViewModel(ISearchServiceClient searchServiceClient)
{
// rest of rx query
}
...
}
43. Simple test – first try
44
[TestMethod]
public void Search_OneLetterWord_NoSearchSentToService()
{
var fakeServiceClient = Substitute.For<ISearchServiceClient>();
var vm = new SearchViewModel(fakeServiceClient);
vm.SearchTerm = "A";
fakeServiceClient.DidNotReceive().SearchAsync("A");
}
46. Simplest Rx Test
Install-Package Microsoft.Reactive.Testing
To simplify the Rx testing, derive your test class from ReactiveTest
47
using Microsoft.Reactive.Testing;
[TestClass]
public class SearchViewModelTests : ReactiveTest
{
// Test Methods
}
47. Test 1: Search is sent after half a sec
48
const long ONE_SECOND = TimeSpan.TicksPerSecond;
[TestMethod]
public void MoreThanThreeLetters_HalfSecondGap_SearchSentToService()
{
var fakeServiceClient = Substitute.For<ISearchServiceClient>();
var fakeConcurrencyProvider = Substitute.For<IConcurrencyProvider>();
var testScheduler = new TestScheduler();
fakeConcurrencyProvider.ReturnsForAll<IScheduler>(testScheduler);
var vm = new SearchViewModel(fakeServiceClient, fakeConcurrencyProvider);
testScheduler.Start();
vm.SearchTerm = "reactive";
testScheduler.AdvanceBy(ONE_SECOND / 2);
fakeServiceClient.Received().SearchAsync("reactive");
}
48. TestScheduler
TestScheduler provides two methods for creating observables:
CreateColdObservable – Creates an observable that emits its value relatively to when each observer
subscribes.
CreateHotObservable – Creates and observable that emits its values regardless to the observer
subscription time, and each emission is configured to the absolute scheduler clock
49
var testScheduler = new TestScheduler();
ITestableObservable<int> coldObservable = testScheduler.CreateColdObservable<int>(
OnNext<int>(20, 1),
OnNext<int>(40, 2),
OnCompleted<int>(60)
);
49. Test 2: first search is discarded if another search happens
50
public void TwoValidWords_SlowSearchThenFastSearch_SecondSearchResultsOnly()
{
var fakeServiceClient = Substitute.For<ISearchServiceClient>();
var fakeConcurrencyProvider = Substitute.For<IConcurrencyProvider>();
var testScheduler = new TestScheduler();
fakeConcurrencyProvider.ReturnsForAll<IScheduler>(testScheduler);
fakeServiceClient.SearchAsync("first").Returns(testScheduler.CreateColdObservable(
OnNext<IEnumerable<string>>(2 * ONE_SECOND, new[] {"first"}), ...);
fakeServiceClient.SearchAsync("second").Returns(testScheduler.CreateColdObservable(
OnNext<IEnumerable<string>>(1, new[] { "second" }), ...);
var vm = new SearchViewModel(fakeServiceClient, fakeConcurrencyProvider);
testScheduler.Start();
vm.SearchTerm = "first";
testScheduler.AdvanceBy(ONE_SECOND);
vm.SearchTerm = "second";
testScheduler.AdvanceBy(5 * ONE_SECOND);
Assert.AreEqual("second", vm.SearchResults.First());
}
50. Summary
Pull vs. Push model
Rx operators
Building Rx queries
Rx Concurrency Model
Virtual Time
Testing Time and Concurrency with TestScheduler
51
51. Your headache relief pill to Asynchronous and
Event based applications
Async
Push
Triggers
Events
Reactive ExtensionsReactive Extensions
52
My name is tamir dresher
Im an architect from codevalue israel and a software engineering lecturer at the ruppin academic center
CodeValue is a consulting company and we are also the proud development center of OzCode the amazing debugging extension for visual studio. We have a booth here at conference, so please go and check it out, youll be amazed how you lived without it.
My book Rx in action is now available at Manning early access program should be published in the next few months.
And that the end of my self promotion(it never hurts right?).
So what are we really here for?