You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试时Dispatcher.Invoke(Action)挂起,PushFrame方法无效求助

问题分析与解决建议

核心问题拆解

  • 测试环境无WPF应用实例,Application.Current为null,导致Dispatcher使用测试线程的Dispatcher,但测试线程无消息循环,Invoke调用直接阻塞。
  • async void的命令执行方法无法被测试代码追踪完成状态,导致手动调用的PushFrame无法正确终止,永久卡住。
  • 测试中未正确处理异步任务的完成时机,依赖未就绪的UI线程逻辑引发死锁。

具体解决步骤

1. 替换async void命令逻辑为async Task

async void在非UI场景(如测试)中会导致任务无法被等待,异常无法被捕获。修改PopulatePeople方法:

private async Task PopulatePeople() // 改为async Task
{
    Progress = 0;
    IsBusy = true;
    try
    {
        await Task.Run(async () =>
        {
            ShowToastMessage("About to get a load of records");
            var restPeople = await peopleRestService.PeopleAllAsync();
            ShowToastMessage("Got a load of records");
            
            List<IBaseViewModel> localList = new List<IBaseViewModel>();

            var pvm = Container.Resolve<IPersonViewModel>();
            pvm.Person = pe;
            pvm.Title = pe.Name;
            pvm.RestPeople = new ObservableCollection<RestPerson>(restPeople.Select(p => new RestPerson(p)));

            localList.Add(pvm);
            GenerateExpensiveViewModels(numberOfPeople, maxVmIndex, vmIndex, localList, restPeople);
            ShowToastMessage("Got them all!");
            UI(() => People = new ObservableCollection<IBaseViewModel>(localList));
        });
    }
    finally
    {
        IsBusy = false;
    }
}

2. 实现支持异步的DelegateCommand

原DelegateCommand不支持异步方法,需扩展适配:

public class AsyncDelegateCommand : IDelegateCommand
{
    public event EventHandler CanExecuteChanged;

    readonly Func<Task> _executeAsync;
    readonly Func<bool> _canExecute;

    public AsyncDelegateCommand(Func<Task> executeAsync, Func<bool> canExecute = null)
    {
        _executeAsync = executeAsync;
        _canExecute = canExecute ?? (() => true);
    }

    public bool CanExecute(object parameter) => _canExecute.Invoke();

    public async void Execute(object parameter) // 保持void实现ICommand接口,内部await异步逻辑
    {
        if (CanExecute(parameter)) await _executeAsync();
    }

    public void Refresh() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

ViewModel中初始化命令:

PopulatePeopleCommand = new AsyncDelegateCommand(PopulatePeople);

3. 测试中Mock Dispatcher调用(最优方案)

测试场景无需依赖真实WPF Dispatcher,直接MockIDispatcherService跳过UI线程限制:

// 提前封装BaseViewModel的UI方法为依赖注入的服务
// BaseViewModel代码:
// private readonly IDispatcherService _dispatcherService;
// public BaseViewModel(IDispatcherService dispatcherService) => _dispatcherService = dispatcherService;
// public void UI(Action action) => _dispatcherService.Invoke(action);

// 测试中设置Mock:直接在当前线程执行Action
dispatcherServiceMock.Setup(s => s.Invoke(It.IsAny<Action>())).Callback<Action>(action => action());

4. 测试中正确等待异步任务完成

通过TaskCompletionSource追踪命令执行状态,避免手动调用PushFrame:

[Fact]
public async Task OpenPersonDetailsAndChangeTheirNameTest() // 改为async Task测试方法
{
    // ... 原有Mock代码保持不变 ...

    var tcs = new TaskCompletionSource<bool>();
    // 拦截UI方法,任务完成时触发tcs
    dispatcherServiceMock.Setup(s => s.Invoke(It.IsAny<Action>()))
        .Callback<Action>(action =>
        {
            action();
            tcs.TrySetResult(true);
        });

    var sut = new MainViewModel(RestMoq.Object, windowServiceMoq.Object, dispatcherServiceMock.Object, designerServiceMock.Object, viewModelRepositoryMock.Object, containerRepositoryMock.Object);

    // 执行命令并等待完成
    if (sut.PopulatePeopleCommand.CanExecute(null))
    {
        sut.PopulatePeopleCommand.Execute(null);
        await tcs.Task; // 等待异步任务完成
    }

    sut.SelectedPerson = sut.People.FirstOrDefault();

    if (sut.OpenPersonDetailsCommand.CanExecute(person.Object))
    {
        sut.OpenPersonDetailsCommand.Execute(person.Object);
    }

    // 验证逻辑
    windowServiceMoq.Verify(s => s.Show(It.IsAny<IBaseViewModel>()), Times.Once());            
    providerMock.Verify(p => p.Resolve(typeof(IWindowService)), Times.AtLeastOnce());
}

关键注意事项

  • 测试中永远避免依赖真实WPF UI组件(如Dispatcher),Mock相关服务是最可靠的解耦方式。
  • 命令执行逻辑优先使用async Task而非async void,便于测试追踪任务状态。
  • 不要在测试中手动调用PushFrame,通过异步等待确保所有任务执行完毕。

内容的提问来源于stack exchange,提问作者XAMlMAX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 15:18:09