Open Closed

Does ABP provides integration to any mock framework like Moq? #1208


User avatar
0
Yaduraj created

I have to test a client in my app which will further be commnicating to a third party rest Api. As of now I would like to Mock or fake that Api in my Unit Testing project. Does ABP provides integration to any mock framework like Moq. I specifically want to Unit Test API Calls. How can I mock API calls in abp framework for Unit Testing using xUnit/Moq

  • ABP Framework version: v3.0.4
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes

11 Answer(s)
  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Hi Yaduraj,

    ABP Framework provides NSubstitute for mocking in testing.

    See an example below:

    public class BookStoreTest : BookStoreApplicationTestBase
    {
        private ICurrentUser currentUser;
    
        protected override void AfterAddApplication(IServiceCollection services)
        {
            // Create a Substitute and replace original one in Service Collection
            currentUser = Substitute.For<ICurrentUser>();
            services.AddSingleton(currentUser);
        }
    
        [Fact]
        public async Task ShouldCurrentUserBeOwnerOfBook()
        {
            var userId = Guid.NewGuid();
            var username = "john.doe";
            
            // You can configure return values of Properties or Methods
            currentUser.Id.Returns(userId);
            currentUser.UserName.Returns(username);
    
            // Just a check if the id same. Also all services in the same IoC container will use that configuration.
            var id = currentUser.Id;
            id.ShouldBe(userId);
        }
    }
    

    I've just showed how to mock ICurrentUser, but you can mock any of your custom services that makes http call to a 3rd party API.

  • User Avatar
    0
    Yaduraj created

    Thank you @enisn

  • User Avatar
    0
    Yaduraj created

    Hi, This is urgent. We are facing some issue in mocking repositories using NSubstitute.

    Can we connect through online session. Please help as soon as possible.

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Hi @Yaduraj,

    Can you share some information about the problem that you faced?

    Otherwise I have to redirect to NSubstitute Documentation

  • User Avatar
    0
    Yaduraj created

    Hi @enisn,

    Thanks for the quick response.

    We are using integration tests as in https://docs.abp.io/en/abp/latest/Testing#integration-tests So there are methods where we are calling a CustomAPIService that makes http call to a 3rd party API and would like to Substitute/Mock those calls. In this configuration if I mock the Repositories, I get SQLite Error 19: 'FOREIGN KEY constraint failed'.. Can't we mix NSubstitute with Integration Tests?

    I request you to have an online session so that I can walk you through the code structure in better way. Becuase its a bit hard to give example as some of AppService contains around 30 dependencies

    It will be really helpful. Thanks in Advance!

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Can you share your project of part of project with us and we can see problem.

    You can send it to info@abp.io with issue number (#1208) then we'll reproduce problem & see what's wrong.

  • User Avatar
    0
    Yaduraj created

    Hi @enisn

    The project is very large and could not be send over mail. It would be really great to have an online session where I can show you the code. Please can you guide my how can I get assisted with abp support team, where I can show my code?

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Hi @Yaduraj

    Can you provide a simple scenario to reproduce problem? I think problem occurs according to your dependencies, For example, you've mocked a Repository but it doesn't return foreign key id or mocked id doesn't match with related entity's id.

    Otherwise, in integration test, you shouldn't mock repositories, you should use already seeded datas for testing. Let me introduce a full example that would explain what I meant:

    Think there is a PurchaseAppService but this service uses IPaymentService to call a 3rd party API and make payment. Following example shows how to mock 3rd Party PaymentService

    • A couple of DTOs:
    public class PaymentResult
    {
        public string Code { get; set; }
        public bool Succeeded { get; set; }
    }
    
    public class PaymentInput
    {
        public decimal Price { get; set; }
        public string SomePaymentInfo { get; set; }
    }
    
    public class PurchaseInput
    {
        public Guid ProductId { get; set; }
        public int Amount { get; set; }
    }
    
    • And interfaces:
    public interface IPaymentService : ITransientDependency
    {
        Task<PaymentResult> MakePaymentAsync(PaymentInput input);
    }
    
    public interface IPurchaseAppService : IApplicationService
    {
        Task PurchaseAsync(PurchaseInput input);
    }
    
    • See the implementation of PurchaseAppService, that calls a 3rd party service over its interface.
    public class PurchaseAppService : ApplicationService, IPurchaseAppService
    {
        protected IPaymentService PaymentService { get; }
        protected IRepository<Product> ProductRepository { get; }
    
        public PurchaseAppService(
            IPaymentService paymentService,
            IRepository<Product> productRepository)
        {
            PaymentService = paymentService;
            ProductRepository = productRepository;
        }
    
        public async Task PurchaseAsync(PurchaseInput input)
        {
            var product = await ProductRepository.GetAsync(x => x.Id == input.ProductId);
    
            // 3rd Party Service Call
            var paymentResult = await PaymentService.MakePaymentAsync(new PaymentInput
            {
                Price = product.Price * input.Amount,
                SomePaymentInfo = "Some additrional data here..."
            });
    
            if (!paymentResult.Succeeded)
            {
                throw new BusinessException(message: "Payment is failed...");
            }
    
            // ...
            // There might be an insert code into Purchases table...
        }
    }
    

    • Tests: In that case, I've already seeded a product with id Guid.Empty
    public class PurchaseAppServiceTests : ProjectFApplicationTestBase
    {
        private readonly IPurchaseAppService purchaseAppService;
        private IPaymentService paymentService;
    
        public PurchaseAppServiceTests()
        {
            purchaseAppService = GetRequiredService<IPurchaseAppService>();
        }
    
        protected override void AfterAddApplication(IServiceCollection services)
        {
            paymentService = Substitute.For<IPaymentService>();
    
            services.AddSingleton(paymentService);
        }
    
        [Fact]
        public async Task MakePayment_Should_Be_Failed()
        {
            var paymentInput = new PaymentInput { Price = 10, SomePaymentInfo = "..." };
    
            paymentService
                .MakePaymentAsync(paymentInput)
                .Returns(Task.FromResult(new PaymentResult { Code = "9901", Succeeded = false }));
    
            await Should.ThrowAsync<BusinessException>(async () =>
                await purchaseAppService.PurchaseAsync(new PurchaseInput
                {
                    ProductId = Guid.Empty,
                    Amount = 2
                })
            );
        }
    
        [Fact]
        public async Task MakePayment_Should_Be_Succeeded()
        {
            var paymentInput = new PaymentInput { Price = 10, SomePaymentInfo = "..." };
    
            paymentService
                .MakePaymentAsync(paymentInput)
                .Returns(Task.FromResult(new PaymentResult { Code = "0000", Succeeded = true }));
    
            await Should.NotThrowAsync(async () =>
                await purchaseAppService.PurchaseAsync(new PurchaseInput
                {
                    ProductId = Guid.Empty,
                    Amount = 2
                })
            );
        }
    }
    

    That test scenario mocks IPaymentService methods with no problem.

    I highly suggest to check your all injected services and to make sure your seeder seeds consistent data.

    And if you still think something is wrong, Feel free to share your scenario with code blocks

  • User Avatar
    1
    lalitChougule created
    • ABP Framework version: v3.0.4
    • UI type: Angular
    • DB provider: EF Core
    • Tiered (MVC) or Identity Server Separated (Angular): yes

    Hi,

    I was trying to mock IdentityUserManager

    Error :

    Can not instantiate proxy of class: Volo.Abp.Identity.IdentityUserManager.
    Could not find a parameterless constructor.
    

    StackTrace :

       at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)
       at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors)
       at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.CreateProxyUsingCastleProxyGenerator(Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments, IInterceptor[] interceptors, ProxyGenerationOptions proxyGenerationOptions)
       at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateTypeProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
       at NSubstitute.Proxies.CastleDynamicProxy.CastleDynamicProxyFactory.GenerateProxy(ICallRouter callRouter, Type typeToProxy, Type[] additionalInterfaces, Object[] constructorArguments)
       at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments, Boolean callBaseByDefault)
       at NSubstitute.Core.SubstituteFactory.Create(Type[] typesToProxy, Object[] constructorArguments)
       at NSubstitute.Substitute.For(Type[] typesToProxy, Object[] constructorArguments)
       at NSubstitute.Substitute.For[T](Object[] constructorArguments)
       at SCV.Litmus.LitmusIdentity.LitmusUserInfoApplicationTests.AfterAddApplication(IServiceCollection services) in D:\Litmus\Projects\ap-ar-dashboard\SCV.Litmus\aspnet-core\modules\litmus-core\test\SCV.Litmus.Application.Tests\LitmusIdentity\LitmusUserInfoApplicationTests.cs:line 26
       at Volo.Abp.Testing.AbpIntegratedTest`1..ctor()
       at SCV.Litmus.LitmusTestBase`1..ctor()
       at SCV.Litmus.LitmusApplicationTestBase..ctor()
       at SCV.Litmus.LitmusIdentity.LitmusUserInfoApplicationTests..ctor() in D:\Litmus\Projects\ap-ar-dashboard\SCV.Litmus\aspnet-core\modules\litmus-core\test\SCV.Litmus.Application.Tests\LitmusIdentity\LitmusUserInfoApplicationTests.cs:line 19
    

    My Code :

    public class LitmusUserInfoApplicationTests : LitmusApplicationTestBase
        {
            private readonly ILitmusUserInfoAppService _litmusUserInfoAppService;
    
            private IdentityUserManager _identityUserManager;
    
            public LitmusUserInfoApplicationTests()
            {
                _litmusUserInfoAppService = GetRequiredService&lt;ILitmusUserInfoAppService&gt;();
            }
    
            protected override void AfterAddApplication(IServiceCollection services)
            {
                _identityUserManager = Substitute.For&lt;IdentityUserManager&gt;();
                services.AddSingleton(_identityUserManager);
            }
    
            [Fact]
            public async Task GetSellerQuestionnaireForMakerCheckAsync()
            {
                _identityUserManager.FindByIdAsync("39faa1bb-6509-6a7d-b17f-0deee2cf47db")
                    .Returns(Task.FromResult(JsonConvert.DeserializeObject&lt;IdentityUser&gt;(@"{
                          'TenantId': 'd1be844b-d3a2-031a-f036-39f5d4380239',
                          'UserName': 'Seller.Admin',
                          'NormalizedUserName': 'SELLER.ADMIN',
                          'Name': 'Seller',
                          'Surname': 'Admin',
                          'Email': 'seller.admin@ness.com',
                          'NormalizedEmail': 'LALIT.CHOUGULE@NESS.COM',
                          'EmailConfirmed': false,
                          'PasswordHash': 'AQAAAAEAACcQAAAAEOK/7B0qge7madNnCVW5zGiVxa7sgIYc7XIKR/wG+fuDFn2g28hDGmd/i34RV/Rc8Q==',
                          'SecurityStamp': 'CUICLIBP3N5Z7NV52SKWF2MHBRIGDEPZ',
                          'PhoneNumber': null,
                          'PhoneNumberConfirmed': false,
                          'TwoFactorEnabled': true,
                          'LockoutEnd': null,
                          'LockoutEnabled': true,
                          'AccessFailedCount': 0,
                          'Roles': [
                            {
                              'TenantId': 'd1be844b-d3a2-031a-f036-39f5d4380239',
                              'UserId': '39fd4aa9-8553-35c0-4312-0d7b5f01b810',
                              'RoleId': '39fc43a0-e722-2bc3-7d24-9cc6992ccd3a'
                            }
                          ],
                          'Claims': [],
                          'Logins': [],
                          'Tokens': [],
                          'OrganizationUnits': [],
                          'IsDeleted': false,
                          'DeleterId': null,
                          'DeletionTime': null,
                          'LastModificationTime': '2021-06-23T16:22:02.440744',
                          'LastModifierId': null,
                          'CreationTime': '2021-06-23T16:18:53.936857',
                          'CreatorId': '1c26f3a7-fa8f-dbaf-406b-39f5d443409f',
                          'ExtraProperties': {
                            'PasswordSetDate': '2021-09-21T10:50:14.4543311Z',
                            'HaveOptedMakerChecker': true
                          },
                          'ConcurrencyStamp': '5224e087605a4f498a9684556b5506dc',
                          'Id': '39fd4aa9-8553-35c0-4312-0d7b5f01b810'
                        }")));
    
                var mock_sellerRequesterUsers = @"[
                  {
                    'TenantId': 'd1be844b-d3a2-031a-f036-39f5d4380239',
                    'UserName': 'Seller.Requester',
                    'NormalizedUserName': 'SELLER.REQUESTER',
                    'Name': 'Seller',
                    'Surname': 'Requester',
                    'Email': 'Kaustubh.Kale@ness.com',
                    'NormalizedEmail': 'KAUSTUBH.KALE@NESS.COM',
                    'EmailConfirmed': false,
                    'PasswordHash': 'AQAAAAEAACcQAAAAEFCPsMT+tOATxKorhUkndGa1/k3SOjnx3+emm2sWafeYlVvcnDFnSeP0k5uXlgYJwA==',
                    'SecurityStamp': 'NUUY6ROI2NTZ7UBM2FBDHZBKP5UPXS7B',
                    'PhoneNumber': null,
                    'PhoneNumberConfirmed': false,
                    'TwoFactorEnabled': true,
                    'LockoutEnd': null,
                    'LockoutEnabled': true,
                    'AccessFailedCount': 0,
                    'Roles': null,
                    'Claims': null,
                    'Logins': null,
                    'Tokens': null,
                    'OrganizationUnits': null,
                    'IsDeleted': false,
                    'DeleterId': null,
                    'DeletionTime': null,
                    'LastModificationTime': '2021-06-23T16:30:40.180889',
                    'LastModifierId': '1c26f3a7-fa8f-dbaf-406b-39f5d443409f',
                    'CreationTime': '2021-06-23T16:30:29.23093',
                    'CreatorId': '1c26f3a7-fa8f-dbaf-406b-39f5d443409f',
                    'ExtraProperties': {
                      'PasswordSetDate': '2021-09-21T11:00:28.4022237Z',
                      'HaveOptedMakerChecker': false
                    },
                    'ConcurrencyStamp': '0b7b68128a81401fbc6fa3f91e36b3f3',
                    'Id': '39fd4ab4-2271-7518-4964-a131224f8919'
                  }
                ]";
    
                _identityUserManager.GetUsersInRoleAsync("SellerRequester")
                    .Returns(Task.FromResult(JsonConvert.DeserializeObject&lt;IList&lt;IdentityUser&gt;>(mock_sellerRequesterUsers)));
    
                var mock_sellerApproverUsers = @"[
                  {
                    'TenantId': 'd1be844b-d3a2-031a-f036-39f5d4380239',
                    'UserName': 'Seller.Approver',
                    'NormalizedUserName': 'SELLER.APPROVER',
                    'Name': 'Seller',
                    'Surname': 'Approver',
                    'Email': 'Yaduraj.Shakti@ness.com',
                    'NormalizedEmail': 'YADURAJ.SHAKTI@NESS.COM',
                    'EmailConfirmed': false,
                    'PasswordHash': 'AQAAAAEAACcQAAAAEJkl+HrzdX1HQPWcLTvhmgtiibs2F4pX4avyk4UxK6iHFSCa5Ca4/VyNxVgyY47xIA==',
                    'SecurityStamp': 'TUOGMEIA2RJSFMXCKARFLZO2XSK2QWCA',
                    'PhoneNumber': null,
                    'PhoneNumberConfirmed': false,
                    'TwoFactorEnabled': true,
                    'LockoutEnd': null,
                    'LockoutEnabled': true,
                    'AccessFailedCount': 0,
                    'Roles': null,
                    'Claims': null,
                    'Logins': null,
                    'Tokens': null,
                    'OrganizationUnits': null,
                    'IsDeleted': false,
                    'DeleterId': null,
                    'DeletionTime': null,
                    'LastModificationTime': '2021-06-23T16:29:45.804466',
                    'LastModifierId': '1c26f3a7-fa8f-dbaf-406b-39f5d443409f',
                    'CreationTime': '2021-06-23T16:29:33.925747',
                    'CreatorId': '1c26f3a7-fa8f-dbaf-406b-39f5d443409f',
                    'ExtraProperties': {
                      'PasswordSetDate': '2021-09-21T10:59:32.8141291Z',
                      'HaveOptedMakerChecker': false
                    },
                    'ConcurrencyStamp': '7d9f64db3fa74b2b827940360d2b5185',
                    'Id': '39fd4ab3-494d-9f88-56ac-e9b402130a4d'
                  }
                ]";
    
                _identityUserManager.GetUsersInRoleAsync("SellerApprover")
                   .Returns(Task.FromResult(JsonConvert.DeserializeObject&lt;IList&lt;IdentityUser&gt;>(mock_sellerApproverUsers)));
    
                var result = await _litmusUserInfoAppService.GetSellerQuestionnaireInfoAsync();
            }
        }
    

    How do I mock IdentityUserManager ?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi lalitChougule

    Please create a class that repleace IdentityUserManager in you test project instead of mock it.

    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IdentityUserManager))]
    public class MyIdentityUserManager : IdentityUserManager
    {
        public MyIdentityUserManager(IdentityUserStore store, IIdentityRoleRepository roleRepository,
            IIdentityUserRepository userRepository, IOptions<IdentityOptions> optionsAccessor,
            IPasswordHasher<IdentityUser> passwordHasher, IEnumerable<IUserValidator<IdentityUser>> userValidators,
            IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators, ILookupNormalizer keyNormalizer,
            IdentityErrorDescriber errors, IServiceProvider services, ILogger<IdentityUserManager> logger,
            ICancellationTokenProvider cancellationTokenProvider,
            IOrganizationUnitRepository organizationUnitRepository, ISettingProvider settingProvider) : base(store,
            roleRepository, userRepository, optionsAccessor, passwordHasher, userValidators, passwordValidators,
            keyNormalizer, errors, services, logger, cancellationTokenProvider, organizationUnitRepository,
            settingProvider)
        {
        }
    
        public override Task<IdentityUser> FindByIdAsync(string userId)
        {
            if (userId == "39faa1bb-6509-6a7d-b17f-0deee2cf47db")
            {
                return Task.FromResult(new IdentityUser( //...))
            }
    
            return base.FindByIdAsync(userId);
        }
    }
    
  • User Avatar
    0
    ServiceBot created
    Support Team Automatic process manager

    This question has been automatically marked as stale because it has not had recent activity.

Made with ❤️ on ABP v9.1.0-preview. Updated on December 26, 2024, 06:07