Base solution for your next web application
Open Closed

Unit Testing Question #551


User avatar
0
amwdrizz created

I am running into an error with my unit tests that fail when testing. But the same code, when in production; executes correctly.

For Ex: I have this unit test function

[Fact]
        public void Get_Model_List()
        {
            var models = _modelAppService.GetModelList(new GetModelInput());
            models.Items.Count.ShouldBe(2);
        }

This returns an error, well ShouldBe resolves to 0 as models is empty. The GetModelList function is below

public ListResultOutput<ModelListDto> GetModelList(GetModelInput input)
        {
            var models = _modelRepository
                .GetAll()
                .Include(p => p.Manufacture)
                .Include(p => p.ModelType)
                .WhereIf(
                    !input.Filter.IsNullOrEmpty(),
                    p => p.Name.Contains(input.Filter)
                )
                .OrderBy(p => p.Name)
                .ToList();
            return new ListResultOutput<ModelListDto>(models.MapTo<List<ModelListDto>>());
        }

That function (above) when called in a production setting returns data correctly. But when that function is called via Unit testing, it returns 0.

Now, I ran some debug checks on the test code. I can see that the seed method is seeding the in-ram db correctly. Other unit tests work. They test the manufacture side of the code; they all work. For ex: This works.

[Fact]
        public void Should_Get_All_Manufactures_Without_Filter()
        {
            var manufactures = _manufactureAppService.GetManufacture(new GetManufactureInput());

            manufactures.Items.Count.ShouldBe(2);
        }

Even moving my Model unit test code to the manufacture class results in an error. The only thing I can think of, is ABP is disposing of the DB before running the Model unit tests.

And for the sake of clarity, below is the entire Model unit test .cs file.

using System;
using Onyxeye.Inventory.Model;
using Onyxeye.Inventory.Model.Inputs;
using Onyxeye.Tests.Sessions;
using Onyxeye.Users;
using Shouldly;
using Xunit;

namespace Onyxeye.Tests.Inventory
{
    public class ModelAppService_Test : OnyxeyeTestBase
    {
        private readonly IModelAppService _modelAppService;

        public ModelAppService_Test()
        {
            _modelAppService = Resolve<IModelAppService>();
        }

        [Fact]
        public void Get_Model_List()
        {
            var models = _modelAppService.GetModelList(new GetModelInput());
            models.Items.Count.ShouldBe(2);
        }
    }
}

12 Answer(s)
  • User Avatar
    0
    hikalkan created
    Support Team

    Very strange. Are you sure there is Modal entities in dbcontext (resolve your dbcontext and query it manually). Also, does your Modal implements IMustHaveTenant or IMayHaveTenant which may filter entities automatically.

  • User Avatar
    0
    amwdrizz created

    <cite>hikalkan: </cite> Also, does your Modal implements IMustHaveTenant or IMayHaveTenant which may filter entities automatically.

    That would be the problem, I glossed over adding the tenantid to the seed method. Thank you for pointing that out.

  • User Avatar
    0
    gpcaretti created

    This is the same issue I have with tenant data.

    All my tests running on non-tenant data works, but when I call the AppService working on tenant data it always return 0.

    I tried to disable filters, set the correct tenant Id without success.

    This is the test

    public async Task GetShowCase_Test() {
                // I call the DB just to be sure the query returns the correct # of items (16)
                IList<PropertyForSale> dbShowCase = null;
                await UsingDbContextAsync(async context => {
                    dbShowCase = await context.REProperties.OfType<PropertyForSale>()
                                            .Include(p => p.SaleAgreement)
                                            .Where(p => p.SaleAgreement == null)
                                            .ToListAsync();
                });
    
                var input = new GetPfsShowCaseInput {
                    MaxResultCount = 100,
                    SkipCount = 0
                };
    
                var output = await _pfsAppService.GetShowCase(input);
                output.Items.Count.ShouldBeGreaterThan(0);  // this fails as items are 0!
            }
    

    This is the service method that works when I run it normally

    public async Task<GetPfsShowCaseOutput> GetShowCase(GetPfsShowCaseInput input) {
                    // this returns 0 tiems during unit test
                    var items = _propertiesManager.GetShowCase(input.SkipCount, input.MaxResultCount, input.Sorting);
                    return new GetPfsShowCaseOutput {
                        Items = items.MapTo<ReadOnlyCollection<PropertyForSaleDto>>()
                    };
                }
            }
    

    And this is the Repository code:

    public IQueryable<PropertyForSale> GetShowCase(int skipCount, int maxResultCount, string sorting = null) {
                var query = GetAll()
                    .Where(p => (p.ShowOnline && (p.SaleAgreement == null)));
    
                // sorting and paging
                query = query
                    .OrderByDescending(p => p.ShowOnlineDate);
                query = query
                    .Skip(skipCount)
                    .Take(maxResultCount);
                return query;
            }
    
  • User Avatar
    0
    hikalkan created
    Support Team

    A few questions:

    1. What are tenantid values of these 16 rows?
    2. Is REProperties entity IMustHaveTenant or IMayHaveTenant?
    3. Did you try to set AbpSession.TenantId to a desired value in unit test code?
  • User Avatar
    0
    gpcaretti created

    Well,

    1. I set the TenantId to 1 and seeding in MyAppTestBase:
    //Seed initial data for default tenant
                AbpSession.TenantId = 1;
                UsingDbContext(context =>
                {
                    new TenantRoleAndUserBuilder(context, AbpSession.GetTenantId()).Create();
                    new DefaultTenantBusinessDataCreator(context, AbpSession.GetTenantId()).Create(20); // Gp - create app business data               
                });
    

    It is you original code. My code is just the last line (// Gp ...)

    1. REProperties entity implements IMustHaveTenant

    TenantId is correctly 1, when I extract it from DB:

    PropertyForSale pfs = null;
                await UsingDbContextAsync(async context => {
                    pfs = await context.REProperties.OfType<PropertyForSale>()
                                    .Include(p => p.Owner)
                                    .Include(p => p.Solicitor)
                                    .Include(p => p.ZipCode)
                                    .Include(p => p.City)
                                    .Include(p => p.Attachments)
                                    .Include(p => p.SaleAgreement)
                                    .FirstAsync();
                });
    

    But when I execute the line

    _myRepository.FirstOrDefaultAsync(p => p.Id == pfsId)
    

    or

    AbpSession.TenantId = 1;
             . . .
           _myRepository.FirstOrDefaultAsync(p => p.Id == pfsId)
    

    it returns nothing.

    even the code _myRepository.GetAllList() returns an empty list.

    1. Yes I did :-(
  • User Avatar
    0
    hikalkan created
    Support Team

    As I see, the only difference in queries is the repository has "p.ShowOnline" condition in Where method. Maybe that's the reason. If not, is that working while running te project (not testing)? If not working, you may try to see what query is executed from the SQL Server profiler.

  • User Avatar
    0
    gpcaretti created

    Thanks again for your reply. I went on with tests and I think it might be a bug somewhere (in your code?) as I found a workaround to solve the issue:

    if I ran the same test by calling first a _userAppService.FindUser(...), everything works! See my working new testing code:

    [Fact]
    public async Task GetPropertyFull_Test() {
        PropertyForSale entity = null;
        await UsingDbContextAsync(AbpSession.TenantId, async context => {
            entity = await context.REProperties.OfType<PropertyForSale>()
                .FirstAsync(u => (u.TenantId == AbpSession.TenantId));
        });
        // only needed for a possible bug!
        await _userAppService.FindUser(long.MaxValue); // only needed for a possible bug!
    
        // test
        var output = await _pfsAppService.GetPropertyFull(entity.Id);
        output.ShouldNotBeNull();
        output.Id.ShouldBe(entity.Id);
    }
    

    This test works! But if I comment out code lines related to user, specifically the line:

    var userDto = await _userAppService.FindUser(long.MaxValue); // only needed for a possible bug!
    

    the test fails.

    The test works even if I move the 'finduser' code in the Test's constructor leaving my test method clean:

    public PfsAppService_Tests() {
        _pfsAppService = Resolve<IPfsAppService>();
    
        // only needed for a possible bug!
        _userAppService = Resolve<IUserAppService>();
        _userAppService.FindUser(long.MaxValue);
    }
    
    [Fact]
    public async Task GetPropertyFull_Test() {
        PropertyForSale entity = null;
        await UsingDbContextAsync(AbpSession.TenantId, async context => {
            entity = await context.REProperties.OfType<PropertyForSale>()
                .FirstAsync(u => (u.TenantId == AbpSession.TenantId));
        });
    
        // test
        var output = await _pfsAppService.GetPropertyFull(entity.Id);
        output.ShouldNotBeNull();
        output.Id.ShouldBe(entity.Id);
    }
    

    What I miss?

  • User Avatar
    0
    guillaumej created

    I have exactly the same problem.

    In the TestBase Contructor, I create some seed data which is correctly initialized (with a valid TenantId)

    But as soon as I am in my unit tests, the data is not found by my Application service (I checked that the current Tenant Id is the right one)

  • User Avatar
    0
    guillaumej created

    Going further : So, when I create an entity using Dbcontext, I can't find it by ApplicationService.List (when testing) BUT, if in my unit test, I create another entity using the ApplicationService.Create function, then ApplicationService.list returns both entities !

    ( Both the list and the Create function are using the basic IRepository implementation)

    That's quite weird.

  • User Avatar
    0
    hikalkan created
    Support Team

    this maybe related to that: <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1163">https://github.com/aspnetboilerplate/as ... ssues/1163</a> After v0.10 release, please try it again.

  • User Avatar
    0
    gpcaretti created

    Going further to this point, I got the exception received on my unit test.

    When I run this code:

    [Fact]
    public async Task GetPropertyFull_Test() {
        PropertyForSale entity = null;
        await UsingDbContextAsync(AbpSession.TenantId, async context => {
            entity = await context.REProperties.OfType<PropertyForSale>()
                .FirstAsync(u => (u.TenantId == AbpSession.TenantId));
        });
       . . .
    }
    

    The exception I receive when fetching the entity is:

    System.TypeInitializationException : The type initializer for 'Effort.DbConnectionFactory' threw an exception.
    ---- Effort.Exceptions.EffortException : The Effort library failed to register its provider automatically, so manual registration is required.
    
    a) Call the Effort.Provider.EffortProviderConfiguration.RegisterProvider() method at entry point of the application
    
    or
    
    b) Add the following configuration to the App.config file:
       <system.data>
          <DbProviderFactories>
             <add name="Effort.Provider"
                  invariant="Effort.Provider"
                  description="Effort.Provider"
                  type="Effort.Provider.EffortProviderFactory, Effort" />
          </DbProviderFactories>
       </system.data>
    
       <entityFramework>
          <providers>
             <provider invariantName="Effort.Provider"
                       type="Effort.Provider.EffortProviderServices, Effort" />
          </providers>
       </entityFramework>
    

    But If I add this line of code (theoretically not neede) at the beginning of the code, all works:

    [Fact]
    public async Task GetPropertyFull_Test() {
         var dummyService = Resolve<IUserAppService>();  // why?
    
        PropertyForSale entity = null;
        await UsingDbContextAsync(AbpSession.TenantId, async context => {
        ...
    }
    

    Anybody can help me?

    Tnx, gp

  • User Avatar
    0
    gpcaretti created

    <cite>hikalkan: </cite> this maybe related to that: <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1163">https://github.com/aspnetboilerplate/as ... ssues/1163</a> After v0.10 release, please try it again.

    It is still there, but I went on with my investigation and I was able to get the exception thrown when running the code:

    await UsingDbContextAsync(AbpSession.TenantId, async context => {
            entity = await context.REProperties.OfType<PropertyForSale>()
                .FirstAsync(u => (u.TenantId == AbpSession.TenantId));
        });
    

    I receive this error:

    Result StackTrace:	
    at Effort.DbConnectionFactory.CreateTransient()
       at MyNewHouse.Tests.MyNewHouseTestBase.UseSingleDatabase() in C:\WA\GpES\MyNewHouse\Tests\MyNewHouse.Tests\MyNewHouseTestBase.cs:line 68
       at MyNewHouse.Tests.MyNewHouseTestBase.PreInitialize() in C:\WA\GpES\MyNewHouse\Tests\MyNewHouse.Tests\MyNewHouseTestBase.cs:line 60
       at Abp.TestBase.AbpIntegratedTestBase`1.InitializeAbp() in D:\Halil\GitHub\aspnetboilerplate\src\Abp.TestBase\TestBase\AbpIntegratedTestBase.cs:line 42
       at Abp.TestBase.AbpIntegratedTestBase`1..ctor(Boolean initializeAbp) in D:\Halil\GitHub\aspnetboilerplate\src\Abp.TestBase\TestBase\AbpIntegratedTestBase.cs:line 34
       at Abp.TestBase.AbpIntegratedTestBase`1..ctor(Boolean initializeAbp) in D:\Halil\GitHub\aspnetboilerplate\src\Abp.TestBase\TestBase\AbpIntegratedTestBase.cs:line 34
       at MyNewHouse.Tests.MyNewHouseTestBase..ctor() in C:\WA\GpES\MyNewHouse\Tests\MyNewHouse.Tests\MyNewHouseTestBase.cs:line 27
       at MyNewHouse.Tests.Locations.CityAppService_Tests..ctor() in C:\WA\GpES\MyNewHouse\Tests\MyNewHouse.Tests\Locations\CityAppService_Tests.cs:line 24
       at MyNewHouse.Tests.Auctions.AuctionAppService_Tests..ctor() in C:\WA\GpES\MyNewHouse\Tests\MyNewHouse.Tests\Auctions\AuctionAppService_Tests.cs:line 22
    ----- Inner Stack Trace -----
       at Effort.Provider.EffortProviderConfiguration.RegisterDbConfigurationEventHandler()
       at Effort.Provider.EffortProviderConfiguration.RegisterProvider()
       at Effort.DbConnectionFactory..cctor()
    ----- Inner Stack Trace -----
       at System.Data.Entity.Infrastructure.DependencyResolution.DbConfigurationManager.AddLoadedHandler(EventHandler`1 handler)
       at System.Data.Entity.DbConfiguration.add_Loaded(EventHandler`1 value)
       at Effort.Provider.EffortProviderConfiguration.RegisterDbConfigurationEventHandler()
    Result Message:	
    System.TypeInitializationException : The type initializer for 'Effort.DbConnectionFactory' threw an exception.
    ---- Effort.Exceptions.EffortException : The Effort library failed to register its provider automatically, so manual registration is required.
    
    a) Call the Effort.Provider.EffortProviderConfiguration.RegisterProvider() method at entry point of the application
    
    or
    
    b) Add the following configuration to the App.config file:
       &lt;system.data&gt;
          &lt;DbProviderFactories&gt;
             &lt;add name=&quot;Effort.Provider&quot;
                  invariant=&quot;Effort.Provider&quot;
                  description=&quot;Effort.Provider&quot;
                  type=&quot;Effort.Provider.EffortProviderFactory, Effort&quot; /&gt;
          &lt;/DbProviderFactories&gt;
       &lt;/system.data&gt;
    
    
       &lt;entityFramework&gt;
          &lt;providers&gt;
             &lt;provider invariantName=&quot;Effort.Provider&quot;
                       type=&quot;Effort.Provider.EffortProviderServices, Effort&quot; /&gt;
          &lt;/providers&gt;
       &lt;/entityFramework&gt;
    -------- System.InvalidOperationException : The Entity Framework was already using a DbConfiguration instance before an attempt was made to add an 'Loaded' event handler. 'Loaded' event handlers can only be added as part of application start up before the Entity Framework is used. See http://go.microsoft.com/fwlink/?LinkId=260883 for more information.
    

    I hope I helped you. Gp