Skip to content

Commit 7b4d98c

Browse files
committed
move code over from PR
1 parent 5a4ef36 commit 7b4d98c

13 files changed

+882
-28
lines changed

FluentAssertions.Reactive.sln

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26124.0
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30330.147
55
MinimumVisualStudioVersion = 15.0.26124.0
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{00D5194F-4335-4B15-B486-7DB272B65547}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentAssertions.Reactive", "Src\FluentAssertions.Reactive\FluentAssertions.Reactive.csproj", "{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Reactive", "Src\FluentAssertions.Reactive\FluentAssertions.Reactive.csproj", "{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{FF4404AB-62EB-4964-AA9C-E62364EC6952}"
11+
EndProject
12+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Reactive.Specs", "Tests\FluentAssertions.Reactive.Specs\FluentAssertions.Reactive.Specs.csproj", "{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}"
13+
EndProject
14+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0593589C-673C-47EB-8B56-E6B83DA2580E}"
15+
ProjectSection(SolutionItems) = preProject
16+
.editorconfig = .editorconfig
17+
EndProjectSection
918
EndProject
1019
Global
1120
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -16,9 +25,6 @@ Global
1625
Release|x64 = Release|x64
1726
Release|x86 = Release|x86
1827
EndGlobalSection
19-
GlobalSection(SolutionProperties) = preSolution
20-
HideSolutionNode = FALSE
21-
EndGlobalSection
2228
GlobalSection(ProjectConfigurationPlatforms) = postSolution
2329
{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2430
{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -32,8 +38,27 @@ Global
3238
{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}.Release|x64.Build.0 = Release|Any CPU
3339
{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}.Release|x86.ActiveCfg = Release|Any CPU
3440
{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2}.Release|x86.Build.0 = Release|Any CPU
41+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
43+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Debug|x64.ActiveCfg = Debug|Any CPU
44+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Debug|x64.Build.0 = Debug|Any CPU
45+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Debug|x86.ActiveCfg = Debug|Any CPU
46+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Debug|x86.Build.0 = Debug|Any CPU
47+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
48+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Release|Any CPU.Build.0 = Release|Any CPU
49+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Release|x64.ActiveCfg = Release|Any CPU
50+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Release|x64.Build.0 = Release|Any CPU
51+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Release|x86.ActiveCfg = Release|Any CPU
52+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F}.Release|x86.Build.0 = Release|Any CPU
53+
EndGlobalSection
54+
GlobalSection(SolutionProperties) = preSolution
55+
HideSolutionNode = FALSE
3556
EndGlobalSection
3657
GlobalSection(NestedProjects) = preSolution
3758
{FBBA1F0F-2782-4C58-9F66-2CFCBAF043C2} = {00D5194F-4335-4B15-B486-7DB272B65547}
59+
{F7FA72E3-365F-4F5C-8B5B-377DDE9B8B2F} = {FF4404AB-62EB-4964-AA9C-E62364EC6952}
60+
EndGlobalSection
61+
GlobalSection(ExtensibilityGlobals) = postSolution
62+
SolutionGuid = {FB99E965-4EAB-437C-A80D-DB5934C53CE6}
3863
EndGlobalSection
3964
EndGlobal

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# FluentAssertions.Reactive
2+
3+
FluentAssertions for Observables

Src/FluentAssertions.Reactive/Class1.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

Src/FluentAssertions.Reactive/FluentAssertions.Reactive.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@
44
<TargetFramework>netstandard2.0</TargetFramework>
55
</PropertyGroup>
66

7+
<ItemGroup>
8+
<PackageReference Include="FluentAssertions" Version="5.10.3" />
9+
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.4.1" />
10+
</ItemGroup>
11+
712
</Project>
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Reactive;
6+
using System.Reactive.Concurrency;
7+
using System.Reactive.Disposables;
8+
using System.Reactive.Linq;
9+
using System.Threading;
10+
using Microsoft.Reactive.Testing;
11+
12+
namespace FluentAssertions.Reactive
13+
{
14+
/// <summary>
15+
/// Observer for testing <see cref="Observable"/>s using the FluentAssertions framework
16+
/// </summary>
17+
/// <typeparam name="TPayload"></typeparam>
18+
public class FluentTestObserver<TPayload> : IObserver<TPayload>, IDisposable
19+
{
20+
private readonly IDisposable _subscription;
21+
private readonly IScheduler _observeScheduler;
22+
private readonly RollingReplaySubject<Recorded<Notification<TPayload>>> _rollingReplaySubject = new RollingReplaySubject<Recorded<Notification<TPayload>>>();
23+
24+
/// <summary>
25+
/// The observable which is observed by this instance
26+
/// </summary>
27+
public IObservable<TPayload> Subject { get; }
28+
29+
/// <summary>
30+
/// The stream of recorded <see cref="Notification{T}"/>s
31+
/// </summary>
32+
public IObservable<Recorded<Notification<TPayload>>> RecordedNotificationStream => _rollingReplaySubject.AsObservable();
33+
34+
/// <summary>
35+
/// The recorded <see cref="Notification{T}"/>s
36+
/// </summary>
37+
public IEnumerable<Recorded<Notification<TPayload>>> RecordedNotifications =>
38+
_rollingReplaySubject.GetSnapshot();
39+
40+
/// <summary>
41+
/// The recorded messages
42+
/// </summary>
43+
public IEnumerable<TPayload> RecordedMessages =>
44+
RecordedNotifications.GetMessages();
45+
46+
/// <summary>
47+
/// The exception
48+
/// </summary>
49+
public Exception Error =>
50+
RecordedNotifications
51+
.Where(r => r.Value.Kind == NotificationKind.OnError)
52+
.Select(r => r.Value.Exception)
53+
.FirstOrDefault();
54+
55+
/// <summary>
56+
/// The recorded messages
57+
/// </summary>
58+
public bool Completed =>
59+
RecordedNotifications
60+
.Any(r => r.Value.Kind == NotificationKind.OnCompleted);
61+
62+
/// <summary>
63+
/// Creates a new <see cref="FluentTestObserver{TPayload}"/> which subscribes to the supplied <see cref="IObservable{T}"/>
64+
/// </summary>
65+
/// <param name="subject">the <see cref="IObservable{T}"/> under test</param>
66+
public FluentTestObserver(IObservable<TPayload> subject)
67+
{
68+
Subject = subject;
69+
_observeScheduler = new EventLoopScheduler();
70+
_subscription = new CompositeDisposable(); subject.ObserveOn(_observeScheduler).Subscribe(this);
71+
}
72+
73+
/// <summary>
74+
/// Creates a new <see cref="FluentTestObserver{TPayload}"/> which subscribes to the supplied <see cref="IObservable{T}"/>
75+
/// </summary>
76+
/// <param name="subject">the <see cref="IObservable{T}"/> under test</param>
77+
public FluentTestObserver(IObservable<TPayload> subject, IScheduler scheduler)
78+
{
79+
Subject = subject;
80+
_observeScheduler = scheduler;
81+
_subscription = subject.ObserveOn(scheduler).Subscribe(this);
82+
}
83+
84+
/// <summary>
85+
/// Creates a new <see cref="FluentTestObserver{TPayload}"/> which subscribes to the supplied <see cref="IObservable{T}"/>
86+
/// </summary>
87+
/// <param name="subject">the <see cref="IObservable{T}"/> under test</param>
88+
public FluentTestObserver(IObservable<TPayload> subject, TestScheduler testScheduler)
89+
{
90+
Subject = subject;
91+
_observeScheduler = testScheduler;
92+
_subscription = subject.ObserveOn(Scheduler.CurrentThread).Subscribe(this);
93+
}
94+
95+
/// <summary>
96+
/// Clears the recorded notifications and messages as well as the recorded notifications stream buffer
97+
/// </summary>
98+
public void Clear() => _rollingReplaySubject.Clear();
99+
100+
/// <inheritdoc />
101+
public void OnNext(TPayload value)
102+
{
103+
_rollingReplaySubject.OnNext(
104+
new Recorded<Notification<TPayload>>(_observeScheduler.Now.UtcTicks, Notification.CreateOnNext(value)));
105+
}
106+
107+
/// <inheritdoc />
108+
public void OnError(Exception exception) =>
109+
_rollingReplaySubject.OnNext(new Recorded<Notification<TPayload>>(_observeScheduler.Now.UtcTicks, Notification.CreateOnError<TPayload>(exception)));
110+
111+
/// <inheritdoc />
112+
public void OnCompleted() =>
113+
_rollingReplaySubject.OnNext(new Recorded<Notification<TPayload>>(_observeScheduler.Now.UtcTicks, Notification.CreateOnCompleted<TPayload>()));
114+
115+
/// <inheritdoc />
116+
public void Dispose()
117+
{
118+
_subscription?.Dispose();
119+
_rollingReplaySubject?.Dispose();
120+
}
121+
122+
/// <summary>
123+
/// Returns an <see cref="ReactiveAssertions{TPayload}"/> object that can be used to assert the observed <see cref="IObservable{T}"/>
124+
/// </summary>
125+
/// <returns></returns>
126+
public ReactiveAssertions<TPayload> Should() => new ReactiveAssertions<TPayload>(this);
127+
}
128+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace FluentAssertions.Reactive
5+
{
6+
internal static class NoSynchronizationContextScope
7+
{
8+
public static DisposingAction Enter()
9+
{
10+
var context = SynchronizationContext.Current;
11+
SynchronizationContext.SetSynchronizationContext(null);
12+
return new DisposingAction(() => SynchronizationContext.SetSynchronizationContext(context));
13+
}
14+
15+
internal class DisposingAction : IDisposable
16+
{
17+
private readonly Action action;
18+
19+
public DisposingAction(Action action)
20+
{
21+
this.action = action;
22+
}
23+
24+
public void Dispose()
25+
{
26+
action();
27+
}
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)