Интеграционные тесты

1. Добавил Docker Compose
2. Добавил AppFactory
3. Добавлен базовый класс для интеграционных классов
4. Покрыл тестами контроллер AdminDeposit
This commit is contained in:
Степанов Дмитрий 2023-12-12 19:13:07 +05:00
parent fd40a3b930
commit 2aa897d3df
6 changed files with 418 additions and 0 deletions

View File

@ -19,8 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.25" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.6.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -0,0 +1,47 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using AsbCloudApp.Data.User;
using AsbCloudDb;
using AsbCloudDb.Model;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace AsbCloudWebApi.Tests.IntegrationTests;
public abstract class BaseIntegrationTest : IClassFixture<TestWebApplicationFactory>
{
private readonly IAsbCloudDbContext dbContext;
protected readonly HttpClient httpClient;
protected readonly IServiceScope scope;
protected BaseIntegrationTest(TestWebApplicationFactory factory)
{
scope = factory.Services.CreateScope();
httpClient = factory.CreateClient();
dbContext = scope.ServiceProvider.GetRequiredService<IAsbCloudDbContext>();
dbContext.Database.EnsureCreatedAndMigrated();
}
protected void SetToken(string token)
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
//TODO: поправить метод регистрации, сделать этот метод следующим образом:
//1. Пройти регистрацию
//2. Залогиниться под новым логином и паролем. Тестить всё под учёткой dev -- это неправильно, но пока так.
// Пока используется jwt токен dev, пока так захардкожено
protected Task<UserTokenDto> RegisterUserAsync()
{
var authToken = new UserTokenDto
{
Token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiZGV2IiwiaWRDb21wYW55IjoiMSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InJvb3QiLCJuYmYiOjE3MDIzNjg3NzksImV4cCI6MTczMzkyNjM3OSwiaXNzIjoiYSIsImF1ZCI6ImEifQ.zMJHgoEkfifR28vIPxtABvznf8NFJjk33E9fxNZTwGM"
};
return Task.FromResult(authToken);
}
}

View File

@ -0,0 +1,24 @@
using AsbCloudApp.Data;
using AsbCloudApp.Services;
namespace AsbCloudWebApi.Tests.IntegrationTests.Controllers;
public class AdminDepositControllerTests : CrudControllerTests<DepositDto, ICrudRepository<DepositDto>>
{
public AdminDepositControllerTests(TestWebApplicationFactory factory) : base(factory)
{
}
protected override string Uri => "api/admin/deposit";
protected override DepositDto GetFakerData() => new()
{
Caption = "Fake deposit",
Timezone = new SimpleTimezoneDto
{
Hours = 5,
IsOverride = false,
TimezoneId = "Екатеринбург"
}
};
}

View File

@ -0,0 +1,275 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Services;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace AsbCloudWebApi.Tests.IntegrationTests.Controllers;
public abstract class CrudControllerTests<T, TRepository> : BaseIntegrationTest
where T : class, IId
where TRepository : ICrudRepository<T>
{
private readonly TRepository repository;
protected abstract string Uri { get; }
protected abstract T GetFakerData();
protected CrudControllerTests(TestWebApplicationFactory factory)
: base(factory)
{
repository = scope.ServiceProvider.GetRequiredService<TRepository>();
}
[Fact]
public virtual async Task InsertAsync_Should_ReturnsSuccess()
{
//arrange
var fakerData = GetFakerData();
var request = new HttpRequestMessage
{
Content = JsonContent.Create(fakerData),
Method = HttpMethod.Post,
RequestUri = new Uri(Uri, UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<int>();
//assert
Assert.True(result > 0);
Assert.True(response.IsSuccessStatusCode);
}
[Fact]
public virtual async Task InsertRangeAsync_Should_ReturnsSuccess()
{
//arrange
var fakerData = GetFakerData();
var request = new HttpRequestMessage
{
Content = JsonContent.Create(new[] { fakerData }),
Method = HttpMethod.Post,
RequestUri = new Uri($"{Uri}/range", UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<int>();
//assert
Assert.True(result > 0);
Assert.True(response.IsSuccessStatusCode);
}
[Fact]
public virtual async Task InsertRangeAsync_Should_ReturnsBadRequest_WhenInputCollectionFakerDataIsEmpty()
{
//arrange
var request = new HttpRequestMessage
{
Content = JsonContent.Create(Enumerable.Empty<T>()),
Method = HttpMethod.Post,
RequestUri = new Uri($"{Uri}/range", UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
//assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public virtual async Task UpdateAsync_Should_ReturnsSuccess()
{
//arrange
var fakerData = GetFakerData();
fakerData.Id = await repository.InsertAsync(fakerData, default);
var request = new HttpRequestMessage
{
Content = JsonContent.Create(fakerData),
Method = HttpMethod.Put,
RequestUri = new Uri(Uri, UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<int>();
//assert
Assert.Equal(fakerData.Id, result);
Assert.True(response.IsSuccessStatusCode);
}
[Fact]
public virtual async Task UpdateAsync_Should_ReturnsBadRequest_WhenFakerDataHasInvalidId()
{
//arrange
var fakerData = GetFakerData();
var request = new HttpRequestMessage
{
Content = JsonContent.Create(fakerData),
Method = HttpMethod.Put,
RequestUri = new Uri(Uri, UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
//assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public virtual async Task DeleteAsync_Should_ReturnsSuccess()
{
//arrange
var fakerData = GetFakerData();
fakerData.Id = await repository.InsertAsync(fakerData, default);
var request = new HttpRequestMessage
{
Method = HttpMethod.Delete,
RequestUri = new Uri($"{Uri}/{fakerData.Id}", UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<int>();
//assert
Assert.True(result > 0);
Assert.True(response.IsSuccessStatusCode);
}
[Fact]
public virtual async Task DeleteAsync_Should_ReturnsNoContent_WhenFakerDataHasInvalidId()
{
//arrange
const int fakerInvalidId = 0;
var request = new HttpRequestMessage
{
Method = HttpMethod.Delete,
RequestUri = new Uri($"{Uri}/{fakerInvalidId}", UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
//assert
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
public virtual async Task GetOrDefaultAsync_Should_ReturnsSuccess()
{
//arrange
var fakerData = GetFakerData();
fakerData.Id = await repository.InsertAsync(fakerData, default);
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri($"{Uri}/{fakerData.Id}", UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<T>();
//assert
Assert.NotNull(result);
Assert.Equal(fakerData.Id, result.Id);
Assert.True(response.IsSuccessStatusCode);
}
[Fact]
public virtual async Task GetOrDefaultAsync_Should_ReturnsNoContent_WhenFakerDataHasInvalidId()
{
//arrange
const int fakerInvalidId = 0;
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri($"{Uri}/{fakerInvalidId}", UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
//assert
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
public virtual async Task GetAllAsync_Should_ReturnsSuccess()
{
//arrange
var existingFakerDataIds = (await repository.GetAllAsync(default)).Select(f => f.Id);
foreach (var existingFakerDataId in existingFakerDataIds)
await repository.DeleteAsync(existingFakerDataId, default);
var fakerData = GetFakerData();
fakerData.Id = await repository.InsertAsync(fakerData, default);
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(Uri, UriKind.Relative)
};
var user = await RegisterUserAsync();
SetToken(user.Token);
//act
var response = await httpClient.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<T[]>();
//assert
Assert.NotNull(result);
Assert.Single(result);
Assert.True(response.IsSuccessStatusCode);
}
}

View File

@ -0,0 +1,46 @@
using System.Linq;
using System.Threading.Tasks;
using AsbCloudDb.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Testcontainers.PostgreSql;
using Xunit;
namespace AsbCloudWebApi.Tests.IntegrationTests;
public class TestWebApplicationFactory : WebApplicationFactory<Startup>,
IAsyncLifetime
{
private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder()
.WithImage("timescale/timescaledb:latest-pg15")
.WithDatabase("AsbCloud")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AsbCloudDbContext>));
if (descriptor is not null)
services.Remove(descriptor);
services.AddDbContext<AsbCloudDbContext>(options =>
options.UseNpgsql(_dbContainer.GetConnectionString()));
});
}
public Task InitializeAsync()
{
return _dbContainer.StartAsync();
}
public new Task DisposeAsync()
{
return _dbContainer.StopAsync();
}
}

24
docker-compose.yml Normal file
View File

@ -0,0 +1,24 @@
version: '3.9'
services:
postgres:
container_name: pg_db
image: timescale/timescaledb:latest-pg15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: "AsbCloud"
volumes:
- ./timescaledb_data:/var/lib/postgresql/data
ports:
- "5434:5432"
pgadmin:
container_name: pgadmin
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: noemail@noemail.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"