【问题标题】:How to unit test exceptions from ReactiveCommand (ReactiveUI)?如何对来自 ReactiveCommand (ReactiveUI) 的异常进行单元测试?
【发布时间】:2020-02-26 13:20:47
【问题描述】:

我正在使用 xUnit 和 ReactiveUI 11.2(对于 WPF,.NET Framework 4.8,但我认为我的问题更笼统)。

基本上,我想在 ViewModel 中测试我的 ReactiveCommand

例如,有一些条件在我的代码中引发了异常,我的程序崩溃了。

我想做一个单元测试来重现这个错误(单元测试应该失败),然后我会修复我的错误,以某种方式防止异常,然后我的测试应该通过以反映修复。 (相当标准的程序)。

问题是,在 ReactiveCommand 期间抛出的任何异常似乎都被 ReactiveUI “吞噬”了,异常不会使测试失败

此外,如果我尝试在 .Subscribe() 的回调中编写 Assert() 语句,也会发生同样的情况:我可以在调试期间看到我的断言正确失败,但无论如何测试都以绿色标记为“通过”。

我尝试了不同的方式,与调度程序一起玩了一下,但没有任何改进。 我尝试使用所描述的“.ThrownExceptions”也无济于事。

这里有一些文档:https://reactiveui.net/docs/handbook/testing/


TL;DR

如何在我的ReactiveCommands 中设置异常导致我的单元测试失败?我应该如何完全对ReactiveCommands 进行单元测试?


以下是演示该问题的完整程序。

与 NuGet 包一起使用: xunit 2.4.1, xunit.runner.visualstudio 2.4.1, ReactiveUI.Testing 11.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 失败。

【问题讨论】:

  • 我相信你可以使用awaitIObservable 可等待。
  • @Aluan Haddad,谢谢,但无法编译。 IObservable&lt;Foo&gt; does not contain a definition for 'GetAwaiter' ...
  • 应该...你运行的是哪个版本?
  • @AluanHaddad 我可以看到 System.Reactive 4.3.2 作为 ReactiveUI 的依赖项
  • @AluanHaddad 我刚刚注意到 IObservable&lt;T&gt; 是在 vanilla .NET 框架 (4.8) 的 System 命名空间中定义的。而且它看起来并不“等待”,它只是继承自 IDisposable

标签: c# unit-testing xunit reactiveui


【解决方案1】:

经过一些尝试以及此文档的帮助:http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html,我终于找到了丢失的东西。

需要:

  • 使用 TestScheduler
  • 并告诉测试调度程序运行(它将立即执行 observable 中的所有内容)

所以这个问题中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

【讨论】:

    【解决方案2】:

    @Pac0,试试这个。您需要添加 FluentAssertions 包。我建议也定义一个自定义异常。

    // Arrange
    var foo = new Foo();            
    
    // Act
    var result = foo.TestCommand.Execute().Subscribe();
    
    // Assert
    result.Should().BeOfType<Exception>();
    

    【讨论】:

    • 我试试看!谢谢,是的,非自定义异常只是为了minimal reproducible example。与此同时,我还找到了一个使用调度程序的解决方案,它似乎给出了正确的结果。
    • 这个包很有趣,但是它用另一个 :System.Reactive.ObserveOnObserverLongRunning 代替了问题,无论我的方法Explode() 做什么(抛出或和平返回),所以它不能解决问题,因为即使没有问题,测试也总是会失败。还是谢谢!
    猜你喜欢
    • 2011-06-06
    • 1970-01-01
    • 2012-10-29
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    • 2022-12-21
    • 2011-08-11
    • 1970-01-01
    相关资源
    最近更新 更多