我ItemsViewModel
中LoadItems()
了一个方法,它应该使用 SQLite 数据库中的项目填充视图模型的属性。
如何对此方法进行单元测试,以确保使用数据库中的内容正确更新 Items 属性?
我想我必须以某种方式模拟数据库,但我无法弄清楚如何在不重写LoadItems()
方法本身的情况下做到这一点。
public class ItemsViewModel : BaseViewModel
{
public ObservableCollection<Item> Items { get; set; }
public ItemsViewModel() { ... }
void LoadItems()
{
Items.Clear();
var items = App.Database.GetItems();
foreach (var item in items)
Items.Add(item);
}
}
这就是依赖注入的全部内容:不要将其他类型的static
成员用于外部服务,如IO(数据库等)。
(并且您的数据库操作应该async
)
将您的类更改为以下内容:
public class ItemsViewModel : BaseViewModel
{
private readonly IDatabase db;
public ItemsViewModel( IDatabase database )
{
this.db = database ?? throw new ArgumentNullException(nameof(database));
this.Items = new ObservableCollection<Item>();
}
public ObservableCollection<Item> Items { get; }
public async Task LoadItemsAsync()
{
// (Show an activity indicator here and disable other inputs)
this.Items.Clear();
var items = await this.db.GetItemsAsync();
this.Items.AddRange( items );
// (Hide the activity indicator here)
}
}
并在 DI 容器中注册数据库:
(我假设您使用的是ViewModelLocator
- 尽管许多人认为这是一种反模式):
public static class MyViewModelLocator
{
private static readonly IContainer _container = RegisterDependencies();
private static IContainer RegisterDependencies()
{
return new ContainerBuilder()
// Register services (this is required):
.RegisterType<IDatabase,MyDatabase>()
// Register consumers (this is optional and only needed if you're using the ViewModelLocator pattern in your XAML views):
.RegisterSingleton<ItemsViewModel>()
.Build();
}
public static IContainer Container => _container;
public static ItemsViewModel ItemsViewModel => _container.Resolve<ItemsViewModel();
}
然后要测试它,您需要提供自己的IDatabase
,要么是为测试创建的,要么是不使用MyViewModelLocator
IClassFixture
(请注意,xUnit 的IClassFixture
与 DI 不同):
public class ItemsTests
{
[Fact]
public async Task Load_items_passes_the_ronseal_challenge()
{
using( IDatabase testDatabase = new FakeDatabase() )
{
ItemsViewModel vm = new ItemsViewModel( db );
Assert.Equal( 0, vm.Items.Count );
await vm.LoadItemsAsync();
Assert.Equal( 5, vm.Items.Count );
}
}
}
或:
public class ItemsTests : IClassFixture<IDatabase>
{
private readonly IDatabase db;
public ItemsTests( IDatabase db )
{
this.db = db ?? throw new ArgumentNullException(nameof(db));
}
[Fact]
public async Task Load_items_passes_the_ronseal_challenge()
{
ItemsViewModel vm = new ItemsViewModel( this.db );
Assert.Equal( 0, vm.Items.Count );
await vm.LoadItemsAsync();
Assert.Equal( 5, vm.Items.Count );
}
}