Unit testing Xamarin.Forms MVVMLight应用程序的Xunit测试工作不正常,并且从未停止运行

Unit testing Xamarin.Forms MVVMLight应用程序的Xunit测试工作不正常,并且从未停止运行,unit-testing,xamarin,xamarin.forms,mvvm-light,xunit,Unit Testing,Xamarin,Xamarin.forms,Mvvm Light,Xunit,我正在学习为Xamarin应用程序编写单元测试用例。我使用MVVMLight和ServiceLocator创建了一个非常基本的表单应用程序。我有一个登录页面和一个要测试的LoginViewModel。我已经创建了一个Xunit测试项目来测试LoginViewModel。首先,我创建了一个通过测试(事实),检查测试项目是否正常工作。它运行良好,通过顺利 在那之后,我用InlineData编写了另一个理论来测试我的登录命令的功能,在那里发生了错误,为登录命令编写的测试从未停止运行,也没有通过或失败测

我正在学习为Xamarin应用程序编写单元测试用例。我使用MVVMLight和ServiceLocator创建了一个非常基本的表单应用程序。我有一个登录页面和一个要测试的LoginViewModel。我已经创建了一个Xunit测试项目来测试LoginViewModel。首先,我创建了一个通过测试(事实),检查测试项目是否正常工作。它运行良好,通过顺利

在那之后,我用InlineData编写了另一个理论来测试我的登录命令的功能,在那里发生了错误,为登录命令编写的测试从未停止运行,也没有通过或失败测试

我在下面提到我的两门课,如果有人能看一下,告诉我我做错了什么

 public class LoginViewModel : ViewModelBase
{
    /// <summary>
    /// Initializes a new instance of the <see cref="T:MVVMArchitecture.ViewModels.LoginViewModel"/> class.
    /// </summary>
    /// <param name="navigation"> Navigation </param>
    /// <param name="dialog"> Dialog</param>
    public LoginViewModel(INavigationService navigation, IDialogService dialog) : base(navigation, dialog)
    {
        Title = PageTitles.LoginTitle;
        Validator = new UserValidator();
        ValidationContext = new ValidationContext<User>(User, new PropertyChain(), new RulesetValidatorSelector(new[] { "Login", "Password" }));
    }

    #region Properties
    User user = new User();

    public User User
    {
        get => user;
        set { SetProperty(ref user, value); }
    }

    RelayCommand loginCommand;

    public RelayCommand LoginCommand
    {
        get => loginCommand ?? (loginCommand = new RelayCommand(LoginUser, () => !IsBusy));
    }

    #endregion

    #region Action Hanlders
    async void LoginUser()
    {
        if (IsBusy)
            return;
        IsBusy = true;
        LoginCommand.RaiseCanExecuteChanged();

        var validationResults = Validator.Validate(ValidationContext);
        if (validationResults.IsValid)
        {
            var loggedIn = await LoginFlow();
            if (loggedIn)
            {
                Device.BeginInvokeOnMainThread(() =>
                {
                    var navPage = new NavigationPage(new HomePage())
                    {
                        BarBackgroundColor = (Color)Application.Current.Resources["AppThemeColor"],
                        BarTextColor = Color.White
                    };
                    App.ConfigureMainPage(navPage);
                });
            }
            else
            {
                IsBusy = false;
                await DialogService.ShowMessage(AuthenticationAlerts.InvalidCredentials, AuthenticationAlerts.LoginFailed);
            }
        }
        else
        {
            IsBusy = false;
            await DialogService.ShowMessage(validationResults.Errors[0].ErrorMessage, AuthenticationAlerts.LoginFailed);
        }

        IsBusy = false;
        LoginCommand.RaiseCanExecuteChanged();
    }

    /// <summary>
    /// Login flow to login user based on internet and service mode.
    /// </summary>
    /// <returns>The flow.</returns>
    async Task<bool> LoginFlow()
    {
        return await Task.Run(async () => CommonUtils.Instance.IsConnected ? await ServiceManager.Instance.LoginUser(User) : await DataManager.Instance.LoginUser(User));
    }

    #endregion

    // sample method call from code behind
    public void DisplayData(string msg)
    {
        DialogService?.ShowMessageBox(msg, AppConstants.LoginPage);
    }
}
public类LoginViewModel:ViewModelBase
{
/// 
///初始化类的新实例。
/// 
///航行
///对话
public LoginView模型(INavigationService导航,IDialogService对话框):基础(导航,对话框)
{
Title=PageTitles.LoginTitle;
Validator=新的UserValidator();
ValidationContext=new ValidationContext(用户,new PropertyChain(),new RulesetValidatorSelector(new[]{“Login”,“Password”}));
}
#区域属性
用户=新用户();
公共用户
{
get=>user;
set{SetProperty(ref user,value);}
}
RelayCommand逻辑命令;
公共中继命令登录命令
{
get=>loginCommand??(loginCommand=newrelaycommand(logiuser,()=>!IsBusy));
}
#端区
#区域行动汉德尔
异步void登录用户()
{
如果(忙)
返回;
IsBusy=true;
LoginCommand.RaiseCanExecuteChanged();
var validationResults=Validator.Validate(ValidationContext);
if(validationResults.IsValid)
{
var loggedIn=await LoginFlow();
if(loggedIn)
{
Device.beginInvokeMainThread(()=>
{
var navPage=新导航页面(新主页())
{
BarBackgroundColor=(Color)Application.Current.Resources[“AppThemeColor”],
BarTextColor=颜色。白色
};
应用程序配置主页(导航页);
});
}
其他的
{
IsBusy=false;
等待DialogService.ShowMessage(AuthenticationAlerts.InvalidCredentials,AuthenticationAlerts.LoginFailed);
}
}
其他的
{
IsBusy=false;
等待DialogService.ShowMessage(validationResults.Errors[0].ErrorMessage,AuthenticationAlerts.LoginFailed);
}
IsBusy=false;
LoginCommand.RaiseCanExecuteChanged();
}
/// 
///基于internet和服务模式的登录用户登录流程。
/// 
///流动。
异步任务登录流()
{
返回wait Task.Run(async()=>CommonUtils.Instance.IsConnected?wait ServiceManager.Instance.LoginUser(用户):wait DataManager.Instance.LoginUser(用户));
}
#端区
//来自代码隐藏的示例方法调用
公共无效显示数据(字符串消息)
{
DialogService?.ShowMessageBox(消息,AppConstants.LoginPage);
}
}
我的测试班:

public class UnitTest1
{
    App app;
    LoginViewModel loginViewModel;

    public UnitTest1()
    {
        Models.TestingMocks.Init();
        app = new App();
        loginViewModel = new LoginViewModel(ServiceLocator.Current.GetInstance<INavigationService>(),
            ServiceLocator.Current.GetInstance<IDialogService>());
    }

    [Fact]
    public void MustPass()
    {
        int expected = 1;
        int actual = 1;
        Assert.Equal(expected, actual);
        Assert.NotNull(app);
        Assert.NotNull(loginViewModel);
    }

    [Theory]
    [InlineData("ACosta", "Time2Eat!")]
    public void EmptyUsernamePassword(string username, string password)
    {
        var model = CreateViewModelAndLogin(username, password);
        //var v = ServiceManager.Instance.LoginUser(model.User);
        //A.CallTo(() => v.Result).MustHaveHappened();
        Assert.NotNull(model);
    }

    private LoginViewModel CreateViewModelAndLogin(string userName, string password)
    {
        loginViewModel.User = new User
        {
            UserName = userName,
            Password = password
        };

        loginViewModel.LoginCommand.Execute(null);

        return loginViewModel;
    }

}
公共类UnitTest1
{
应用程序;
LoginView模型LoginView模型;
公共单元测试1()
{
Models.TestingMocks.Init();
app=新app();
loginViewModel=新的loginViewModel(ServiceLocator.Current.GetInstance(),
ServiceLocator.Current.GetInstance());
}
[事实]
公共空间通行证()
{
int预期为1;
int实际值=1;
断言。相等(预期、实际);
Assert.NotNull(app);
Assert.NotNull(loginViewModel);
}
[理论]
[InlineData(“ACosta”、“Time2Eat!”)]
public void EmptyUsernamePassword(字符串用户名、字符串密码)
{
var model=CreateViewModelAndLogin(用户名、密码);
//var v=ServiceManager.Instance.LoginUser(model.User);
//A.CallTo(()=>v.Result)。必须发生();
Assert.NotNull(模型);
}
private LoginViewModel CreateViewModelAndLogin(字符串用户名、字符串密码)
{
loginViewModel.User=新用户
{
用户名=用户名,
密码=密码
};
loginViewModel.LoginCommand.Execute(null);
返回loginViewModel;
}
}

谢谢

为什么要使用
异步void
?(注意
async void
最适合异步事件处理程序)这看起来更像是一个集成测试,而不是一个基于视图模型紧密耦合设计的独立单元测试,以及测试时访问的内容。@Nkosi如何使其松散耦合或单元测试而不是集成测试?我使用MVVMLight使应用程序松散耦合,但我对它不熟悉。将登录流提取到它自己的关注点/服务中。还将平台问题(即设备。*)提取到其自身的问题/服务中。显式地将提取的关注点注入视图模型。重构命令以使用异步事件处理程序而不是普通的异步void方法。为什么要使用
async void
?(注意
async void
最适合异步事件处理程序)这看起来更像是一个集成测试,而不是一个基于视图模型紧密耦合设计的独立单元测试,以及测试时访问的内容。@Nkosi如何使其松散耦合或单元测试而不是集成测试?我使用MVVMLight使应用程序松散耦合,但我对它不熟悉。将登录流提取到它自己的关注点/服务中。还将平台问题(即设备。*)提取到其自身的问题/服务中。显式地将提取的关注点注入视图模型。重构命令以使用异步事件处理程序而不是普通的异步v