add some integration test for grpc services

This commit is contained in:
meysamhadeli 2022-06-03 00:16:54 +04:30
parent e2a03daf46
commit 0467daee32
16 changed files with 224 additions and 24 deletions

View File

@ -22,7 +22,6 @@
<PackageReference Include="MagicOnion.Abstractions" Version="4.4.0" />
<PackageReference Include="MagicOnion.Client" Version="4.4.0" />
<PackageReference Include="MagicOnion.Server" Version="4.4.0" />
<PackageReference Include="MagicOnion.Server" Version="4.4.0" />
<PackageReference Include="NSubstitute" Version="4.3.0" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="protobuf-net.BuildTools" Version="3.0.115">
@ -51,7 +50,6 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
<PackageReference Include="MongoDB.Driver" Version="2.14.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OpenTelemetry.Contrib.Instrumentation.MassTransit" Version="1.0.0-beta2" />
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" />
@ -120,6 +118,10 @@
<PackageReference Include="FluentAssertions" Version="6.2.0" />
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="Respawn" Version="4.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.41.0" />
</ItemGroup>

View File

@ -8,7 +8,7 @@ namespace BuildingBlocks.Contracts.Grpc;
{
UnaryResult<FlightResponseDto> GetById(long id);
UnaryResult<IEnumerable<SeatResponseDto>> GetAvailableSeats(long flightId);
UnaryResult<FlightResponseDto> ReserveSeat(ReserveSeatRequestDto request);
UnaryResult<SeatResponseDto> ReserveSeat(ReserveSeatRequestDto request);
}

View File

@ -26,10 +26,13 @@ public class CreateBookingCommandHandler : IRequestHandler<CreateBookingCommand,
_eventStoreDbRepository = eventStoreDbRepository;
var channelFlight = GrpcChannel.ForAddress(grpcOptions.Value.FlightAddress);
_flightGrpcService = new Lazy<IFlightGrpcService>(() => MagicOnionClient.Create<IFlightGrpcService>(channelFlight)).Value;
_flightGrpcService =
new Lazy<IFlightGrpcService>(() => MagicOnionClient.Create<IFlightGrpcService>(channelFlight)).Value;
var channelPassenger = GrpcChannel.ForAddress(grpcOptions.Value.PassengerAddress);
_passengerGrpcService = new Lazy<IPassengerGrpcService>(() => MagicOnionClient.Create<IPassengerGrpcService>(channelPassenger)).Value;
_passengerGrpcService =
new Lazy<IPassengerGrpcService>(() => MagicOnionClient.Create<IPassengerGrpcService>(channelPassenger))
.Value;
}
public async Task<ulong> Handle(CreateBookingCommand command,
@ -38,8 +41,10 @@ public class CreateBookingCommandHandler : IRequestHandler<CreateBookingCommand,
Guard.Against.Null(command, nameof(command));
var flight = await _flightGrpcService.GetById(command.FlightId);
if (flight is null)
throw new FlightNotFoundException();
var passenger = await _passengerGrpcService.GetById(command.PassengerId);
var emptySeat = (await _flightGrpcService.GetAvailableSeats(command.FlightId))?.First();

View File

@ -32,9 +32,9 @@ public class FlightGrpcService : ServiceBase<IFlightGrpcService>, IFlightGrpcSer
return result.Adapt<IEnumerable<SeatResponseDto>>();
}
public async UnaryResult<FlightResponseDto> ReserveSeat(ReserveSeatRequestDto request)
public async UnaryResult<SeatResponseDto> ReserveSeat(ReserveSeatRequestDto request)
{
var result = await _mediator.Send(new ReserveSeatCommand(request.FlightId, request.SeatNumber));
return result.Adapt<FlightResponseDto>();
return result.Adapt<SeatResponseDto>();
}
}

View File

@ -0,0 +1,14 @@
using AutoBogus;
using BuildingBlocks.IdsGenerator;
using Flight.Seats.Features.CreateSeat;
namespace Integration.Test.Fakes;
public class FakeCreateSeatCommand : AutoFaker<CreateSeatCommand>
{
public FakeCreateSeatCommand(long flightId)
{
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
RuleFor(r => r.FlightId, _ => flightId);
}
}

View File

@ -0,0 +1,13 @@
using Flight.Seats.Features.CreateSeat;
using Flight.Seats.Models;
namespace Integration.Test.Fakes;
public static class FakeSeatCreated
{
public static Seat Generate(CreateSeatCommand command)
{
return Seat.Create(command.Id, command.SeatNumber, command.Type, command.Class, command.FlightId);
}
}

View File

@ -1,8 +1,11 @@
using System.Linq;
using System.Threading.Tasks;
using BuildingBlocks.Contracts.Grpc;
using Flight.Flights.Features.GetAvailableFlights;
using FluentAssertions;
using Grpc.Net.Client;
using Integration.Test.Fakes;
using MagicOnion.Client;
using Xunit;
namespace Integration.Test.Flight.Features;
@ -11,10 +14,12 @@ namespace Integration.Test.Flight.Features;
public class GetAvailableFlightsTests
{
private readonly TestFixture _fixture;
private readonly GrpcChannel _channel;
public GetAvailableFlightsTests(TestFixture fixture)
{
_fixture = fixture;
_channel = fixture.Channel;
}
[Fact]

View File

@ -0,0 +1,50 @@
using System.Linq;
using System.Threading.Tasks;
using BuildingBlocks.Contracts.Grpc;
using Flight.Flights.Features.GetAvailableFlights;
using FluentAssertions;
using Grpc.Net.Client;
using Integration.Test.Fakes;
using MagicOnion.Client;
using Xunit;
namespace Integration.Test.Flight.Features;
[Collection(nameof(TestFixture))]
public class GetAvailableSeatsTests
{
private readonly TestFixture _fixture;
private readonly GrpcChannel _channel;
public GetAvailableSeatsTests(TestFixture fixture)
{
_fixture = fixture;
_channel = fixture.Channel;
}
[Fact]
public async Task should_return_available_seats_from_grpc_service()
{
// Arrange
var flightCommand = new FakeCreateFlightCommand().Generate();
var flightEntity = FakeFlightCreated.Generate(flightCommand);
await _fixture.InsertAsync(flightEntity);
var seatCommand1 = new FakeCreateSeatCommand(flightEntity.Id).Generate();
var seatCommand2 = new FakeCreateSeatCommand(flightEntity.Id).Generate();
var seatEntity1 = FakeSeatCreated.Generate(seatCommand1);
var seatEntity2 = FakeSeatCreated.Generate(seatCommand2);
await _fixture.InsertAsync(seatEntity1, seatEntity2);
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
// Act
var response = await flightGrpcClient.GetAvailableSeats(flightEntity.Id);
// Assert
response?.Should().NotBeNull();
response?.Count().Should().BeGreaterOrEqualTo(2);
}
}

View File

@ -1,7 +1,10 @@
using System.Threading.Tasks;
using BuildingBlocks.Contracts.Grpc;
using Flight.Flights.Features.GetFlightById;
using FluentAssertions;
using Grpc.Net.Client;
using Integration.Test.Fakes;
using MagicOnion.Client;
using Xunit;
namespace Integration.Test.Flight.Features;
@ -10,10 +13,12 @@ namespace Integration.Test.Flight.Features;
public class GetFlightByIdTests
{
private readonly TestFixture _fixture;
private readonly GrpcChannel _channel;
public GetFlightByIdTests(TestFixture fixture)
{
_fixture = fixture;
_channel = fixture.Channel;
}
[Fact]
@ -33,4 +38,22 @@ public class GetFlightByIdTests
response.Should().NotBeNull();
response?.Id.Should().Be(flightEntity.Id);
}
[Fact]
public async Task should_retrive_a_flight_by_id_from_grpc_service()
{
// Arrange
var command = new FakeCreateFlightCommand().Generate();
var flightEntity = FakeFlightCreated.Generate(command);
await _fixture.InsertAsync(flightEntity);
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
// Act
var response = await flightGrpcClient.GetById(flightEntity.Id);
// Assert
response?.Should().NotBeNull();
response?.Id.Should().Be(flightEntity.Id);
}
}

View File

@ -0,0 +1,48 @@
using System.Linq;
using System.Threading.Tasks;
using BuildingBlocks.Contracts.Grpc;
using FluentAssertions;
using Grpc.Net.Client;
using Integration.Test.Fakes;
using MagicOnion.Client;
using Xunit;
namespace Integration.Test.Flight.Features;
[Collection(nameof(TestFixture))]
public class ReserveSeatTests
{
private readonly TestFixture _fixture;
private readonly GrpcChannel _channel;
public ReserveSeatTests(TestFixture fixture)
{
_fixture = fixture;
_channel = fixture.Channel;
}
[Fact]
public async Task should_return_valid_reserve_seat_from_grpc_service()
{
// Arrange
var flightCommand = new FakeCreateFlightCommand().Generate();
var flightEntity = FakeFlightCreated.Generate(flightCommand);
await _fixture.InsertAsync(flightEntity);
var seatCommand = new FakeCreateSeatCommand(flightEntity.Id).Generate();
var seatEntity = FakeSeatCreated.Generate(seatCommand);
await _fixture.InsertAsync(seatEntity);
var flightGrpcClient = MagicOnionClient.Create<IFlightGrpcService>(_channel);
// Act
var response = await flightGrpcClient.ReserveSeat(new ReserveSeatRequestDto{ FlightId = seatEntity.FlightId, SeatNumber = seatEntity.SeatNumber });
// Assert
response?.Should().NotBeNull();
response?.SeatNumber.Should().Be(seatEntity.SeatNumber);
response?.FlightId.Should().Be(seatEntity.FlightId);
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Net.Http;
using System.Threading.Channels;
using System.Threading.Tasks;
using BuildingBlocks.Domain.Model;
using BuildingBlocks.EFCore;
@ -7,6 +8,8 @@ using BuildingBlocks.MassTransit;
using BuildingBlocks.Web;
using Flight.Data;
using Flight.Data.Seed;
using Flight.GrpcServer;
using Grpc.Net.Client;
using MassTransit;
using MassTransit.Testing;
using MediatR;
@ -43,9 +46,10 @@ public class TestFixture : IAsyncLifetime
private IServiceScopeFactory _scopeFactory;
private HttpClient _httpClient;
private ITestHarness _testHarness;
private GrpcChannel _channel;
public ITestHarness TestHarness => _testHarness;
public GrpcChannel Channel => _channel;
public async Task InitializeAsync()
{
@ -83,6 +87,11 @@ public class TestFixture : IAsyncLifetime
_httpClient = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
_channel = GrpcChannel.ForAddress(_httpClient.BaseAddress!, new GrpcChannelOptions
{
HttpClient = _httpClient
});
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
await EnsureDatabaseAsync();

View File

@ -5,6 +5,7 @@ using BuildingBlocks.Domain.Model;
using BuildingBlocks.EFCore;
using BuildingBlocks.MassTransit;
using BuildingBlocks.Web;
using Grpc.Net.Client;
using Identity.Data;
using MassTransit;
using MassTransit.Testing;
@ -40,9 +41,9 @@ public class TestFixture : IAsyncLifetime
private WebApplicationFactory<Program> _factory;
private HttpClient _httpClient;
private IServiceScopeFactory _scopeFactory;
private GrpcChannel _channel;
public ITestHarness TestHarness { get; private set; }
public GrpcChannel Channel => _channel;
public async Task InitializeAsync()
{
@ -80,6 +81,8 @@ public class TestFixture : IAsyncLifetime
_httpClient = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
_channel = GrpcChannel.ForAddress(_httpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = _httpClient});
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
await EnsureDatabaseAsync();

View File

@ -0,0 +1,12 @@
using AutoMapper;
using Mapster;
using Passenger.Passengers.Dtos;
namespace Passenger.Passengers.Features;
public class PassengerMappings : IRegister
{
public void Register(TypeAdapterConfig config)
{
}
}

View File

@ -1,12 +0,0 @@
using AutoMapper;
using Passenger.Passengers.Dtos;
namespace Passenger.Passengers.Features;
public class ReservationMappings: Profile
{
public ReservationMappings()
{
CreateMap<Models.Passenger, PassengerResponseDto>();
}
}

View File

@ -1,7 +1,10 @@
using System.Threading.Tasks;
using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.Contracts.Grpc;
using FluentAssertions;
using Grpc.Net.Client;
using Integration.Test.Fakes;
using MagicOnion.Client;
using MassTransit.Testing;
using Passenger.Passengers.Features.GetPassengerById;
using Xunit;
@ -13,11 +16,13 @@ public class GetPassengerByIdTests
{
private readonly TestFixture _fixture;
private readonly ITestHarness _testHarness;
private readonly GrpcChannel _channel;
public GetPassengerByIdTests(TestFixture fixture)
{
_fixture = fixture;
_testHarness = _fixture.TestHarness;
_channel = _fixture.Channel;
}
[Fact]
@ -39,4 +44,24 @@ public class GetPassengerByIdTests
response.Should().NotBeNull();
response?.Id.Should().Be(passengerEntity.Id);
}
[Fact]
public async Task should_retrive_a_passenger_by_id_from_grpc_service()
{
// Arrange
var userCreated = new FakeUserCreated().Generate();
await _testHarness.Bus.Publish(userCreated);
await _testHarness.Consumed.Any<UserCreated>();
var passengerEntity = FakePassengerCreated.Generate(userCreated);
await _fixture.InsertAsync(passengerEntity);
var passengerGrpcClient = MagicOnionClient.Create<IPassengerGrpcService>(_channel);
// Act
var response = await passengerGrpcClient.GetById(passengerEntity.Id);
// Assert
response?.Should().NotBeNull();
response?.Id.Should().Be(passengerEntity.Id);
}
}

View File

@ -5,6 +5,7 @@ using BuildingBlocks.Domain.Model;
using BuildingBlocks.EFCore;
using BuildingBlocks.MassTransit;
using BuildingBlocks.Web;
using Grpc.Net.Client;
using MassTransit;
using MassTransit.Testing;
using MediatR;
@ -40,9 +41,9 @@ public class TestFixture : IAsyncLifetime
private WebApplicationFactory<Program> _factory;
private HttpClient _httpClient;
private IServiceScopeFactory _scopeFactory;
private GrpcChannel _channel;
public ITestHarness TestHarness { get; private set; }
public GrpcChannel Channel => _channel;
public async Task InitializeAsync()
{
@ -79,6 +80,8 @@ public class TestFixture : IAsyncLifetime
_httpClient = _factory.CreateClient(new WebApplicationFactoryClientOptions {AllowAutoRedirect = false});
_channel = GrpcChannel.ForAddress(_httpClient.BaseAddress!, new GrpcChannelOptions {HttpClient = _httpClient});
_checkpoint = new Checkpoint {TablesToIgnore = new[] {"__EFMigrationsHistory"}};
await EnsureDatabaseAsync();