Terry
Terry

Reputation: 61

How to mock Object Mapper

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

Answers (2)

David Liang
David Liang

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:

What I am trying to unit test

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>();

            ...
        }
    }
}

The tests

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

Terry
Terry

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

Related Questions