Starts in:
1 DAY
10 HRS
3 MIN
13 SEC
Starts in:
1 D
10 H
3 M
13 S
Open Closed

Custom ADO Repository Replace IConfiguration during test #1591


User avatar
0
  • ABP Framework version: v4.4.0 RC2
  • UI type: MVC
  • DB provider: Unified EF Core with custom ADO Repositories
  • Tiered (MVC) or Identity Server Separated (Angular): Tiered MVC

In our custom repositories, we're not using EF but rather using ado since we're porting an older system that primarily used stored procedures. It works great, but now that we're setting the connection string using IConfiguration I need to find a way to either inject or substitute IConfiguration with a value that I can use for function testing and I just can't figure out how yet.

I know I can just simply use something in memory like this:

protected override void AfterAddApplication(IServiceCollection services)
{ 
    var inMemorySettings = new Dictionary<string, string> {
        {"ConnectionStrings:Default", "(connection string here to a local mssql instance in a known state)"},
    };

    IConfiguration configuration = new ConfigurationBuilder()
        .AddInMemoryCollection(inMemorySettings)
        .Build();

    //var configuration = Substitute.For<IConfiguration>();

    services.AddSingleton(configuration);
}

public BillingGroupRepositoryTests()
{
    _billingGroupRepository = GetRequiredService<IBillingGroupRepository>();            
}

But when I do, the test host just crashes. When I run tests for these repositories (like IBillingGroupRepository for example) without doing anything, I'll get the somewhat expected "The ConnectionString property has not been initialized."

I've tried AfterAddApplication, BeforeAddApplication, neither helped. The TestBase project doesn't seem to do anything like this since the majority of tests can work just fine in MySql, these just can't because they rely on Stored Procedures.

protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
    base.SetAbpApplicationCreationOptions(options);

    var inMemorySettings = new Dictionary<string, string> {
        {"ConnectionStrings:Default", "(connection string here to a local mssql instance in a known state)"},
    };

    IConfiguration configuration = new ConfigurationBuilder()
        .AddInMemoryCollection(inMemorySettings)
        .Build();

    options.Services.ReplaceConfiguration(configuration);
}

Any ideas?


4 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi Can you share some code of your custom repository? eg IBillingGroupRepository

  • User Avatar
    0

    Looks like the following:

    public class BillingGroupRepository : CabMDRepository<BillingGroup, BillingGroup_Id>, IBillingGroupRepository
        {
            #region Constants
    
            private const string Cmd_BillingGroups_Load = "Billing.BillingGroup_Load_1 ";
            private const string BillingGroupCacheKey = "BillingGroups";
    
            #endregion Constants
    
            #region Static Force Version Check Method
    
            /// <summary>
            /// Force the next load of the entities to do a version check
            /// (In this case, just force a reload)
            /// </summary>
            /// <param name="cache"></param>
            internal static void ForceVersionCheck(IDistributedCache<CacheItem> cache)
            {
                try
                {
                    _semaphore.Wait();
                    cache.Remove(BillingGroupCacheKey);
                }
                finally
                {
                    _semaphore.Release();
                }
            }
    
            /// <summary>
            /// Force the next load of the entities to do a version check
            /// (In this case, just force a reload)
            /// </summary>
            /// <param name="cache"></param>
            internal async static Task ForceVersionCheckAsync(IDistributedCache<CacheItem> cache)
            {
                try
                {
                    await _semaphore.WaitAsync().ConfigureAwait(false);
                    await cache.RemoveAsync(BillingGroupCacheKey).ConfigureAwait(false);
                }
                finally
                {
                    _semaphore.Release();
                }
            }
    
            #endregion Static Force Version Check Method
    
            #region Constructors
    
            public BillingGroupRepository(IAsyncQueryableExecuter asyncExecuter,
                                          ICurrentUser currentUser,
                                          IConfiguration configuration,
                                          IDistributedCache<CacheItem> cache = null) 
                : base(asyncExecuter, currentUser, configuration, cache)  // these go in the regular distributed cache (see ForceVersionCheck above)
            {
            }
    
            #endregion Constructors
    
            public override string EntityTypeName => "Billing Group";
            internal override string CacheKeyPrefix => BillingGroupCacheKey;
    
            #region Override Caching
    
            protected override bool IdIsDefault(BillingGroup_Id Id)
            {
                return Id.Value == default;
            }
    
            protected override void DeleteEntityFromDb(BillingGroup_Id Id)
            {
                throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
            }
    
            protected override Task DeleteEntityFromDbAsync(BillingGroup_Id Id, CancellationToken ct = default)
            {
                throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
            }
    
            protected override VersionAndDelete LoadCurrentVersionFromDb()
            {
                return new VersionAndDelete { Version = 0, LastDeletion = DateTime.MinValue };
            }
    
            protected override DbLoadObj<BillingGroup_Id, BillingGroup> LoadEntitiesFromDb(int fromVersion)
            {
                return Db_LoadBillingGroups(fromVersion);
            }
    
            protected override Task<VersionAndDelete> LoadCurrentVersionFromDbAsync(CancellationToken ct = default)
            {
                return Task.FromResult(new VersionAndDelete { Version = 0, LastDeletion = DateTime.MinValue });
            }
    
            protected async override Task<DbLoadObj<BillingGroup_Id, BillingGroup>> LoadEntitiesFromDbAsync(int fromVersion, CancellationToken ct = default)
            {
                return await Db_LoadBillingGroupsAsync(fromVersion, ct).ConfigureAwait(false);
            }
    
            protected override void SaveEntity(BillingGroup entity)
            {
                throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
            }
    
            protected override Task SaveEntityAsync(BillingGroup entity, CancellationToken ct = default)
            {
                throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
            }
    
            protected override DbSaveObj<BillingGroup> SaveEntityToDb(BillingGroup entity, int currentKnownVersion)
            {
                throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
            }
    
            protected override Task<DbSaveObj<BillingGroup>> SaveEntityToDbAsync(BillingGroup entity, int currentKnownVersion, CancellationToken ct)
            {
                throw new NotSupportedException($"Write operations not enabled for {EntityTypeName}s");
            }
    
            #endregion Override Caching
    
            #region DBAccess
    
            private DbLoadObj<BillingGroup_Id, BillingGroup> Db_LoadBillingGroups(int FromVersion)
            {
                using IDatabase db = new Database(connectionString);
                db.AddParameter(new dbParameter("For15", true));
                DataSet ds = null;
                try
                {
                    Database.ExcuteWithRetry(() => ds = db.Execute<DataSet>(Cmd_BillingGroups_Load), RetryLogger);
                    return new DbLoadObj<BillingGroup_Id, BillingGroup>
                    {
                        List = Db_GetBillingGroupsFromDataSet(ds),
                        Version = 0,
                        AllLoaded = true
                    };
                }
                finally
                {
                    if (ds != null) ds.Dispose();
                }
            }
    
            private async Task<DbLoadObj<BillingGroup_Id, BillingGroup>> Db_LoadBillingGroupsAsync(int FromVersion, CancellationToken ct = default)
            {
                ct.ThrowIfCancellationRequested();
    
                using IDatabase db = new Database(connectionString);
                db.AddParameter(new dbParameter("For15", true));
                DataSet ds = null;
                try
                {
                    await Database.ExcuteWithRetryAsync(async () => ds = await db.ExecuteAsync<DataSet>(Cmd_BillingGroups_Load, ct).ConfigureAwait(false), RetryLogger, ct).ConfigureAwait(false);
                    return new DbLoadObj<BillingGroup_Id, BillingGroup>
                    {
                        List = Db_GetBillingGroupsFromDataSet(ds),
                        Version = 0,
                        AllLoaded = true
                    };
                }
                finally
                {
                    if (ds != null) ds.Dispose();
                }
            }
    
            private static ConcurrentDictionary<BillingGroup_Id, BillingGroup> Db_GetBillingGroupsFromDataSet(DataSet ds)
            {
                // the main list
                var retValue = DataTableConcurrentDictionary.CreateDictionary<BillingGroup_Id, BillingGroup>(ds.Tables[0], "ID");
                // get additional values
                var dvDetail = new DataView(ds.Tables[1], null, "BillingGroupID", DataViewRowState.CurrentRows);
                var dvCharge = new DataView(ds.Tables[2], null, "BillingGroupID", DataViewRowState.CurrentRows);
                var dvTax = new DataView(ds.Tables[3], null, "BillingGroupID", DataViewRowState.CurrentRows);
                // loop through each ba to get associated values
                foreach (var bg in retValue.Values)
                {
                    // details
                    var rows = dvDetail.FindRows(bg.Id).Select(r => r.Row);
                    bg.Detail = rows.Any() ? DataTableList.CreateList<BillingGroupDetail>(rows).FirstOrDefault() : null;
                    // charges
                    rows = dvCharge.FindRows(bg.Id).Select(r => r.Row);
                    bg.Charge = rows.Any() ? DataTableList.CreateList<BillingGroupCharge>(rows).FirstOrDefault() : null;
                    // taxes
                    rows = dvTax.FindRows(bg.Id).Select(r => r.Row);
                    bg.Tax = rows.Any() ? DataTableList.CreateList<BillingGroupTax>(rows).FirstOrDefault() : null;
                }
                return retValue;
            }
    
            #endregion DBAccess
        }
    

    The base CabMDRepository takes IConfiguration so that it can do this:

    connectionString = configuration.GetConnectionString("Default");
    

    And so in the set of tests that I want to run that check these ado/sp loading methods, I want to specify the connection string so that we can actually test.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    There seems to be no problem, can you share a minimal project that reproduces the problem?

    liming.ma@volosoft.com

  • 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 November 20, 2024, 13:06