如何对来自 ReactiveCommand (ReactiveUI) 的异常进行单元测试?
How to unit test exceptions from ReactiveCommand (ReactiveUI)?
我正在使用 xUnit 和 ReactiveUI 11.2(对于 WPF、.NET Framework 4.8,但我认为我的问题更通用)。
基本上,我想在 ViewModels 中测试我的 ReactiveCommand
。
例如,有一些情况在我的代码中抛出异常,我的程序崩溃了。
我想做一个单元测试来重现这个错误(单元测试应该会失败),然后我会修复我的错误,以某种方式防止异常,然后我的测试应该通过以反映修复。 (相当标准的程序)。
问题是,在 ReactiveCommand 期间抛出的任何异常似乎都是 ReactiveUI "swallowed",异常不会使测试失败。
此外,如果我尝试在 .Subscribe()
的回调中编写 Assert()
语句,也会发生同样的情况:我可以在调试期间看到我的断言正确失败,但测试标记为绿色"passed" 无论如何。
我尝试了一些不同的方式来尝试使用调度程序,但没有任何改进。
我尝试按照描述使用“.ThrownExceptions”也无济于事。
这里有一些文档:https://reactiveui.net/docs/handbook/testing/
TL;DR
如何在我的 ReactiveCommand
中设置异常导致我的单元测试失败?我应该如何对 ReactiveCommand
进行完整的单元测试?
下面是演示该问题的完整程序。
与 NuGet 包一起使用:
xunit 2.4.1,
xunit.runner.visualstudio 2.4.1,
ReactiveUI.Testing11.2.1
using Microsoft.Reactive.Testing;
using ReactiveUI;
using ReactiveUI.Testing;
using System;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using Xunit;
namespace Tests
{
public class Foo
{
public ReactiveCommand<Unit, Unit> TestCommand { get; }
public Foo(IScheduler? scheduler = null)
{
scheduler ??= RxApp.MainThreadScheduler;
TestCommand = ReactiveCommand.Create(Explode, canExecute: null, outputScheduler: scheduler);
}
public void Explode()
{
throw new Exception("Boom");
}
}
public class ReactiveCommandTests
{
// Should fail? (it doesn't fail)
[Fact]
public void Test1()
{
var foo = new Foo();
foo.TestCommand.Execute().Subscribe();
}
// Should fail (it fails alright ! no ReactiveUI Observable here...)
[Fact]
public void Test2()
{
var foo = new Foo();
foo.Explode();
}
// Should fail? (it doesn't fail)
[Fact]
public void Test3()
{
var testScheduler = new TestScheduler();
var foo = new Foo(testScheduler);
foo.TestCommand.Execute().Subscribe();
}
// Should fail? (it doesn't fail)
[Fact]
public void Test4()
{
new TestScheduler().With(scheduler =>
{
var foo = new Foo(scheduler);
foo.TestCommand.Execute().Subscribe();
});
}
// Should fail ? (it doesn't fail)
[Fact]
public void Test5()
{
var foo = new Foo();
foo.TestCommand.ThrownExceptions.Subscribe(
(ex) => {
Console.WriteLine("Exception detected !");
Assert.False(true); // This is hit, but doesn't even make the test fail....
});
foo.TestCommand.Execute().Subscribe();
}
}
}
所有测试都会导致抛出异常,所有测试都应该在 IMO 中失败,但只有 未使用 `ReactiveCommand 失败。
@Pac0,试试这个。您需要添加 FluentAssertions 包。我也建议定义自定义异常。
// Arrange
var foo = new Foo();
// Act
var result = foo.TestCommand.Execute().Subscribe();
// Assert
result.Should().BeOfType<Exception>();
经过一些尝试以及此文档的帮助:http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html,我终于找到了丢失的东西。
一个需要:
- 使用 TestScheduler
- 并告诉测试调度程序运行(这将立即执行可观察对象中的所有内容)
所以问题中 Test3 的这种改编似乎工作正常:
[Fact]
public void Test3()
{
var testScheduler = new TestScheduler();
var foo = new Foo(testScheduler);
foo.TestCommand.Execute().Subscribe();
testScheduler.Start(); // YEAY
}
总的来说,我们最终将所有使用 ReactiveCommands
的测试包装在 With
块中:
new TestScheduler().With(scheduler =>
{
var testScheduler = new TestScheduler();
var foo = new Foo(testScheduler);
foo.TestCommand.Execute().Subscribe();
testScheduler.Start(); // YEAY
});
导致测试失败,堆栈跟踪显示它是由异常引起的:
Tests.ReactiveCommandTests.Test3
Source: ReactiveCommandTests.cs line 50
Duration: 45 ms
Message:
System.Exception : Boom
Stack Trace:
Foo.Explode() line 26
<>c__DisplayClass0_0.<Create>b__1(IObserver`1 observer) line 108
CreateWithDisposableObservable`1.SubscribeCore(IObserver`1 observer) line 35
ObservableBase`1.Subscribe(IObserver`1 observer) line 58
--- End of stack trace from previous location where exception was thrown ---
ExceptionDispatchInfo.Throw()
<.cctor>b__2_1(Exception ex) line 16
AnonymousSafeObserver`1.OnError(Exception error) line 62
ObserveOnObserverNew`1.DrainStep(ConcurrentQueue`1 q) line 553
ObserveOnObserverNew`1.DrainShortRunning(IScheduler recursiveScheduler) line 509
<>c__DisplayClass4_0`1.<ScheduleAbsolute>b__0(IScheduler scheduler, TState state1) line 430
ScheduledItem`1.Invoke() line 44
VirtualTimeSchedulerBase`2.Start() line 174
ReactiveCommandTests.Test3() line 56
我正在使用 xUnit 和 ReactiveUI 11.2(对于 WPF、.NET Framework 4.8,但我认为我的问题更通用)。
基本上,我想在 ViewModels 中测试我的 ReactiveCommand
。
例如,有一些情况在我的代码中抛出异常,我的程序崩溃了。
我想做一个单元测试来重现这个错误(单元测试应该会失败),然后我会修复我的错误,以某种方式防止异常,然后我的测试应该通过以反映修复。 (相当标准的程序)。
问题是,在 ReactiveCommand 期间抛出的任何异常似乎都是 ReactiveUI "swallowed",异常不会使测试失败。
此外,如果我尝试在 .Subscribe()
的回调中编写 Assert()
语句,也会发生同样的情况:我可以在调试期间看到我的断言正确失败,但测试标记为绿色"passed" 无论如何。
我尝试了一些不同的方式来尝试使用调度程序,但没有任何改进。 我尝试按照描述使用“.ThrownExceptions”也无济于事。
这里有一些文档:https://reactiveui.net/docs/handbook/testing/
TL;DR
如何在我的 ReactiveCommand
中设置异常导致我的单元测试失败?我应该如何对 ReactiveCommand
进行完整的单元测试?
下面是演示该问题的完整程序。
与 NuGet 包一起使用: xunit 2.4.1, xunit.runner.visualstudio 2.4.1, ReactiveUI.Testing11.2.1
using Microsoft.Reactive.Testing;
using ReactiveUI;
using ReactiveUI.Testing;
using System;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using Xunit;
namespace Tests
{
public class Foo
{
public ReactiveCommand<Unit, Unit> TestCommand { get; }
public Foo(IScheduler? scheduler = null)
{
scheduler ??= RxApp.MainThreadScheduler;
TestCommand = ReactiveCommand.Create(Explode, canExecute: null, outputScheduler: scheduler);
}
public void Explode()
{
throw new Exception("Boom");
}
}
public class ReactiveCommandTests
{
// Should fail? (it doesn't fail)
[Fact]
public void Test1()
{
var foo = new Foo();
foo.TestCommand.Execute().Subscribe();
}
// Should fail (it fails alright ! no ReactiveUI Observable here...)
[Fact]
public void Test2()
{
var foo = new Foo();
foo.Explode();
}
// Should fail? (it doesn't fail)
[Fact]
public void Test3()
{
var testScheduler = new TestScheduler();
var foo = new Foo(testScheduler);
foo.TestCommand.Execute().Subscribe();
}
// Should fail? (it doesn't fail)
[Fact]
public void Test4()
{
new TestScheduler().With(scheduler =>
{
var foo = new Foo(scheduler);
foo.TestCommand.Execute().Subscribe();
});
}
// Should fail ? (it doesn't fail)
[Fact]
public void Test5()
{
var foo = new Foo();
foo.TestCommand.ThrownExceptions.Subscribe(
(ex) => {
Console.WriteLine("Exception detected !");
Assert.False(true); // This is hit, but doesn't even make the test fail....
});
foo.TestCommand.Execute().Subscribe();
}
}
}
所有测试都会导致抛出异常,所有测试都应该在 IMO 中失败,但只有 未使用 `ReactiveCommand 失败。
@Pac0,试试这个。您需要添加 FluentAssertions 包。我也建议定义自定义异常。
// Arrange
var foo = new Foo();
// Act
var result = foo.TestCommand.Execute().Subscribe();
// Assert
result.Should().BeOfType<Exception>();
经过一些尝试以及此文档的帮助:http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html,我终于找到了丢失的东西。
一个需要:
- 使用 TestScheduler
- 并告诉测试调度程序运行(这将立即执行可观察对象中的所有内容)
所以问题中 Test3 的这种改编似乎工作正常:
[Fact]
public void Test3()
{
var testScheduler = new TestScheduler();
var foo = new Foo(testScheduler);
foo.TestCommand.Execute().Subscribe();
testScheduler.Start(); // YEAY
}
总的来说,我们最终将所有使用 ReactiveCommands
的测试包装在 With
块中:
new TestScheduler().With(scheduler =>
{
var testScheduler = new TestScheduler();
var foo = new Foo(testScheduler);
foo.TestCommand.Execute().Subscribe();
testScheduler.Start(); // YEAY
});
导致测试失败,堆栈跟踪显示它是由异常引起的:
Tests.ReactiveCommandTests.Test3
Source: ReactiveCommandTests.cs line 50
Duration: 45 ms
Message:
System.Exception : Boom
Stack Trace:
Foo.Explode() line 26
<>c__DisplayClass0_0.<Create>b__1(IObserver`1 observer) line 108
CreateWithDisposableObservable`1.SubscribeCore(IObserver`1 observer) line 35
ObservableBase`1.Subscribe(IObserver`1 observer) line 58
--- End of stack trace from previous location where exception was thrown ---
ExceptionDispatchInfo.Throw()
<.cctor>b__2_1(Exception ex) line 16
AnonymousSafeObserver`1.OnError(Exception error) line 62
ObserveOnObserverNew`1.DrainStep(ConcurrentQueue`1 q) line 553
ObserveOnObserverNew`1.DrainShortRunning(IScheduler recursiveScheduler) line 509
<>c__DisplayClass4_0`1.<ScheduleAbsolute>b__0(IScheduler scheduler, TState state1) line 430
ScheduledItem`1.Invoke() line 44
VirtualTimeSchedulerBase`2.Start() line 174
ReactiveCommandTests.Test3() line 56