【问题标题】:Why does my async unit test throw an InvalidOperationException while the same sync test doesn't?为什么我的异步单元测试会抛出 InvalidOperationException 而同一个同步测试不会?
【发布时间】:2019-12-18 14:57:45
【问题描述】:

我正在对我的 WPF 项目的一部分进行单元测试,该项目使用 async 代码和 Application.Current.MainWindow 设置主窗口的标题。

public class ClassUnderTest
{
    // Get and apply title async
    public async Task GetAndApplyWindowTitleFromDbAsync()
    {
        string windowTitle = await GetWindowTitleFromDbAsync();
        Application.Current.MainWindow.Title = windowTitle;
    }

    public async Task<string> GetWindowTitleFromDbAsync()
    {
        await Task.Delay(2000);
        return "Async Window Title";
    }

    // Get and apply title sync
    public void GetAndApplyWindowTitleFromDb()
    {
        Application.Current.MainWindow.Title = "Sync Window Title";
    }
}

同步方法单元测试成功,而异步方法在await之后访问Application.Current.MainWindow时抛出如下异常:

System.InvalidOperationException:调用线程无法访问 这个对象,因为另一个线程拥有它。

[TestClass]
public class TestClass
{

    [TestMethod]
    public void SyncMethodWithUICodeTest()
    {
        InitializeApplication();

        Assert.AreEqual("Window Title", Application.Current.MainWindow.Title);

        var classUnderTest = new ClassUnderTest();

        classUnderTest.GetAndApplyWindowTitleFromDb();

        // Test succeeds
        Assert.AreEqual("Sync Window Title", Application.Current.MainWindow.Title);
    }

    [TestMethod]
    public async Task AsyncMethodWithUICodeTest()
    {
        InitializeApplication();

        Assert.AreEqual("Window Title", Application.Current.MainWindow.Title);

        var classUnderTest = new ClassUnderTest();

        await classUnderTest.GetAndApplyWindowTitleFromDbAsync();

        // throws InvalidOperationException
        Assert.AreEqual("Async Window Title", Application.Current.MainWindow.Title);
    }

    private void InitializeApplication()
    {
        if (Application.Current == null)
        {
            var app = new Application();
            var window = new Window();
            app.MainWindow = window;
        }

        Application.Current.MainWindow.Title = "Window Title";
    }
}

我的印象是await 之后的语句返回到“原始”上下文(已知Application.Current.MainWindow)。

为什么async 单元测试会抛出异常? 单元测试的原因是特定的还是我的 WPF 应用程序中也会引发相同的异常?

【问题讨论】:

  • 在具有异步/等待代码的单线程 WPF 应用程序中不会发生这种情况。但是,由于单元测试是在线程池上运行的,因此由 async/await 创建的任务的默认同步上下文不会是拥有可视元素的调度程序。看到这个类似的帖子stackoverflow.com/questions/39385956/…
  • 只是一个愚蠢的问题,为什么你需要通过单元测试来覆盖 UI 逻辑?这是一个 UI 测试的集成范围,IMO
  • 我同意。生产代码的编写没有考虑到单元测试。有对Application.Current.MainWindow(可以去掉)和Application.Current.Dispatcher(可以用接口代替)的引用。我主要担心的是代码不会返回到 WPF 应用程序中的“原始”上下文,这是提供示例的最简单方法。

标签: c# wpf unit-testing async-await


【解决方案1】:

我的印象是 await 之后的语句返回到“原始”上下文(已知 Application.Current.MainWindow)。

它在捕获的SynchronizationContext 上恢复,前提是在您调用await 时有任何上下文要捕获。

在 MSTest 单元测试的上下文中,System.Threading.SynchronizationContext.Current 返回 null,因此一旦您的 Task 完成,就没有上下文可以继续。

您可以尝试通过调用SynchronizationContext.SetSynchronizationContext 来设置SynchronizationContext。 WPF 应用程序默认使用System.Windows.Threading.DispatcherSynchronizationContext

【讨论】:

  • XUnit.NET 有一个同步上下文。
  • @PauloMorgado:但 MSTest 没有。
  • 对不起。我太快了。 MSTest 单元测试没有,但其他人可能(并且确实)有。因此,“在单元测试的上下文中,System.Threading.SynchronizationContext.Current 返回null”并不完全正确。只是对正在使用 XUnit.NET 的人的评论。
  • @PauloMorgado:我编辑了答案以包括“在 MSTest 单元测试的上下文中”以进行澄清。
  • @BrunoV:正确的方法是不在单元测试中测试任何 WPF 框架代码。您应该专注于测试您的代码。提示是阅读 MVVM 模式并针对您的视图模型编写单元测试:stackoverflow.com/a/57008381/7252182
猜你喜欢
  • 2015-11-19
  • 1970-01-01
  • 1970-01-01
  • 2015-07-30
  • 1970-01-01
  • 1970-01-01
  • 2018-02-02
  • 1970-01-01
  • 2019-12-16
相关资源
最近更新 更多