Практикум WPF — тестирование API и unit-тесты
Практикум, шаг 5 из 6. Проверяем API и ViewModel без ручного клика по UI. Подробнее про ASP.NET-тесты — 4516; контекст десктопа — 112.md.
Три уровня проверки TaskDesk
| Уровень | Что проверяем | Инструмент |
|---|---|---|
| Ручной HTTP | Контракт, коды, JSON | Swagger, Postman, curl |
| Интеграция API | Pipeline, маршруты, сериализация | xUnit + WebApplicationFactory |
| Unit ViewModel | Команды, валидация, состояние | xUnit + Moq ITaskRepository |
UI-тесты WPF (FlaUI, WinAppDriver) в базовом маршруте не обязательны — MVVM переносит логику в тестируемые классы.
Ручная проверка в Postman
Коллекция TaskDesk API:
- Environment — переменная
baseUrl=http://localhost:5100. - GET
{{baseUrl}}/api/v1/tasks— пустой массив или список. - POST
{{baseUrl}}/api/v1/tasks— body raw JSON:
{
"title": "Интеграционный тест",
"status": "Todo"
}
- Tests (скрипт Postman):
pm.test("Status 201", () => pm.response.code === 201);
pm.test("Has id", () => pm.response.json().id);
pm.collectionVariables.set("taskId", pm.response.json().id);
- PUT / DELETE с
{{taskId}}.
Негативные кейсы:
POSTс пустымtitle→400;GETс несуществующим GUID →404.
В Development откройте /swagger, нажмите Try it out на Tasks — тот же контракт без импорта коллекции.
Интеграционные тесты API
dotnet new xunit -n TaskDesk.Api.Tests -o tests/TaskDesk.Api.Tests
dotnet add tests/TaskDesk.Api.Tests reference src/TaskDesk.Api
dotnet add tests/TaskDesk.Api.Tests package Microsoft.AspNetCore.Mvc.Testing
public class TasksApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public TasksApiTests(WebApplicationFactory<Program> factory) =>
_client = factory.CreateClient();
[Fact]
public async Task PostTask_ReturnsCreated()
{
var response = await _client.PostAsJsonAsync(
"/api/v1/tasks",
new { title = "Test task", status = "Todo" });
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var dto = await response.Content.ReadFromJsonAsync<TaskDto>();
Assert.NotEqual(Guid.Empty, dto!.Id);
}
[Fact]
public async Task GetTasks_AfterPost_ContainsItem()
{
await _client.PostAsJsonAsync("/api/v1/tasks",
new { title = "Listed", status = "Todo" });
var list = await _client.GetFromJsonAsync<List<TaskDto>>("/api/v1/tasks");
Assert.Contains(list!, t => t.Title == "Listed");
}
}
WebApplicationFactory<Program> поднимает in-memory хост — без реального порта и браузера. Убедитесь, что в TaskDesk.Api есть public partial class Program { } для visibility.
Unit-тесты ViewModel
dotnet new xunit -n TaskDesk.Client.Tests -o tests/TaskDesk.Client.Tests
dotnet add tests/TaskDesk.Client.Tests reference src/TaskDesk.Client
dotnet add tests/TaskDesk.Client.Tests package Moq
public class TaskListViewModelTests
{
[Fact]
public async Task AddTask_WithEmptyTitle_DoesNotCallRepository()
{
var repo = new Mock<ITaskRepository>();
var vm = new TaskListViewModel(repo.Object) { NewTitle = " " };
await vm.AddTaskCommand.ExecuteAsync(null);
repo.Verify(r => r.CreateAsync(It.IsAny<TaskItem>(), default), Times.Never);
}
[Fact]
public async Task AddTask_ValidTitle_CallsRepositoryAndClearsTitle()
{
var repo = new Mock<ITaskRepository>();
repo.Setup(r => r.CreateAsync(It.IsAny<TaskItem>(), default))
.ReturnsAsync((TaskItem t, CancellationToken _) => t);
var vm = new TaskListViewModel(repo.Object) { NewTitle = "Deploy" };
await vm.AddTaskCommand.ExecuteAsync(null);
repo.Verify(r => r.CreateAsync(It.Is<TaskItem>(x => x.Title == "Deploy"), default), Times.Once);
Assert.Equal("", vm.NewTitle);
}
}
ViewModel не создаёт окно — тест выполняется за миллисекунды в CI.
Unit-тест InMemoryTaskStore
[Fact]
public void Delete_RemovesExistingItem()
{
var store = new InMemoryTaskStore();
var item = new TaskItem { Title = "X" };
store.Add(item);
var deleted = store.Delete(item.Id);
Assert.True(deleted);
Assert.Null(store.GetById(item.Id));
}
Матрица «что ломается где»
| Дефект | Поймает |
|---|---|
Неверный route [HttpGet] | Интеграционный тест 404 |
Забыли EnsureSuccessStatusCode в клиенте | Ручной POST + UI error |
CanExecute не обновляется | Unit-тест ViewModel |
Race при ObservableCollection | Редко — нужен UI-тест; на API не влияет |
Запуск всех тестов
dotnet test TaskDesk.sln
В CI (GitHub Actions):
- name: Test
run: dotnet test --no-build --verbosity normal
Чек-лист шага 5
- Postman или Swagger проходит CRUD сценарий.
- Минимум 2 интеграционных теста на API.
- Минимум 2 unit-теста на
TaskListViewModelс Moq. -
dotnet testзелёный локально.
Дальше: Итоговый проект TaskDesk.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). WPF как презентационный слой .NET — дерево XAML, layout, привязки, ресурсы и связь с практикумом TaskDesk. Model, View, ViewModel, INotifyPropertyChanged, ICommand, CommunityToolkit.Mvvm и тестируемая логика для TaskDesk. REST API для TaskDesk — контроллеры, DTO, Swagger, CORS, in-memory хранилище и контракт для WPF-клиента. Prism для WPF — модули, регионы, DI, INavigationService, HttpClient и ApiTaskRepository для TaskDesk.Client. Полноценное клиент-серверное приложение — solution, сборка, сценарии демо, расширения и чек-лист готовности.Практикум WPF — введение в WPF и XAML
Практикум WPF — основы MVVM
Практикум WPF — сервер ASP.NET Core Web API
Практикум WPF — клиент на Prism
Практикум WPF — итоговый проект TaskDesk