Reputation: 61
abp.io framework - testing
I am trying to set an ApplicationService class. The method I'm trying to test uses 'ObjectMapper.Map<classFrom, classTo>(obj)'
I have used NSubstitue as LazyServiceProvider, but I am unable to find the correct Substitute to create an ObjectMapper.
Has anyone done this?
Upvotes: 1
Views: 708
Reputation: 21526
I know there are some documentations from abp showing how to mock IObjectMapper
in integration testing, but I am looking for a way to mock IObjectMapper
in unit tests without bringing in any of those application services in.
I haven't found a better way so far. ABP is lacking documentation on this.
This is what I currently have, but I am not happy about it:
using Volo.Abp.ObjectMapping;
namespace DL.SomeProject.Database.EFCore.Repositories
{
public class SomeRepository : ISomeRepository
{
private readonly SomeDbContext _dbContext;
private readonly IObjectMapper _objectMapper;
public SomeRepository(SomeDbContext dbContext,
IObjectMapper objectMapper)
{
_dbContext = dbContext;
_objectMapper = objectMapper;
}
public async Task<SomeDomainModel?> GetByIdAsync(Guid someId,
CancellationToken cancellationToken = default)
{
var query = ...;
var entity = await _dbContext.SomeEntities
// We're using Postgres, with some data in JSONB columns
.FromSqlRaw(query)
.AsNoTracking()
.SingleOrDefaultAsync(cancellationToken);
if (entity == null)
{
return null;
}
return _objectMapper.Map<SomeEntity, SomeDomainModel>(entity);
}
}
}
And IObjectMapper
is just using AutoMapper
implementation:
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
namespace DL.SomeProject.Database.EFCore
{
[DependsOn(
...
typeof(AbpAutoMapperModule)
)]
public SomeProjectDatabaseEFCoreModule : AbpModule
{
...
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<DbEntityToDomainModelProfile>(validate: true);
});
context.Services.AddAbpDbContext<SomeDbContext>();
...
}
}
}
using AutoMapper;
using EntityFrameworkCore.Testing.NSubstitute;
using EntityFrameworkCore.Testing.NSubstitute.Extensions;
using FluentAssertions;
using FluentAssertions.Execution;
using NSubstitute;
using Volo.Abp.ObjectMapping;
namespace DL.SomeProject.Database.EFCore.Repositories.Tests
{
public class SomeRepositoryTests
{
public class GetByIdAsync
{
[Fact]
public async Task WhenEntityIsFound_ShouldReturnFoundDomainModel()
{
// Arrange
Guid expectedFoundEntityId = Guid.NewGuid();
var data = new List<SomeEntity>
{
new SomeEntity
{
Id = expectedFoundEntityId,
...
}
};
// Mocking the dbContext
var dbContext = Create.MockedDbContextFor<SomeDbContext>();
dbContext.SomeEntities.AddFromSqlRawResult(data);
// !! Here is the workaround !!
// Create an AutoMapper mapper
var autoMapper = new MapperConfiguration(x =>
x.AddProfile<DbEntityToDomainModelProfile>())
.CreateMapper();
// Mock ABP ObjectMapper Map method to use AutoMapper
var objectMapper = Substitute.For<IObjectMapper>();
objectMapper.Map<SomeEntity, SomeDomainModel>(Arg.Any<SomeEntity>())
.Returns(x
=> autoMapper.Map<SomeEntity, SomeDomainModel>(x.Arg<SomeEntity>()));
var sut = new SomeRepository(dbContext, objectMapper);
// Act
var result = await sut.GetByIdAsync(...);
// Assert
using (new AssertionScope())
{
result.Should().NotBeNull();
result!.Id.Should().Be(expectedFoundRecordId);
}
}
}
}
}
Upvotes: 0
Reputation: 61
We resolved the issue.
We used a substitute for LazyServiceProvider.
Then the key was using a very specific setup when the LazyServiceProvider is trying to create the Object Mapper (see the abp code).
_abpProvider = Substitute.For<IAbpLazyServiceProvider>();
_abpProvider.LazyGetService<IObjectMapper>(Arg.Any<Func<IServiceProvider, object>>()).Returns(_objectMapper);
This allowed us to set up our own ObjectMapper in our test method, and have it be used in our ApplicationService.
Upvotes: 1