CoderFunda
  • Home
  • About us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • About us
  • Home
  • Php
  • HTML
  • CSS
  • JavaScript
    • JavaScript
    • Jquery
    • JqueryUI
    • Stock
  • SQL
  • Vue.Js
  • Python
  • Wordpress
  • C++
    • C++
    • C
  • Laravel
    • Laravel
      • Overview
      • Namespaces
      • Middleware
      • Routing
      • Configuration
      • Application Structure
      • Installation
    • Overview
  • DBMS
    • DBMS
      • PL/SQL
      • SQLite
      • MongoDB
      • Cassandra
      • MySQL
      • Oracle
      • CouchDB
      • Neo4j
      • DB2
      • Quiz
    • Overview
  • Entertainment
    • TV Series Update
    • Movie Review
    • Movie Review
  • More
    • Vue. Js
    • Php Question
    • Php Interview Question
    • Laravel Interview Question
    • SQL Interview Question
    • IAS Interview Question
    • PCS Interview Question
    • Technology
    • Other

08 July, 2024

.Net 8 XUnit: Use an In-Memory DBConnection for testing as a replacement for the real MySqlConnection

 Programing Coderfunda     July 08, 2024     No comments   

I'm creating tests for my .Net 8 API, and as I want to test with fake self created data (instead of using the real MySql connection) I'm having problems figuring out how to implement that behavior, I mean, using -for example- an In-Memory database.


We use CQRS (Mediator pattern) for the database queries, and on each query handler we inject ICloudStackCoreConnectionFactory which includes (among others) a CreateConnection method that returns a DBConnection (in our case, a MySqlConnection):


ICloudStackCoreConnectionFactory:
public DbConnection CreateConnection(Region region)
{
return (DbConnection)new MySqlConnection(GetConnString(region));
}



I'd like to replace that MySqlConnection implementation by an in-memory connection so I can test with a reduced set of fake data, but I don't know how to do it.


We use a "Lab" to create all stuff needed for the tests, and the Lab includes a create method as follows:
public static Lab Create(ITestOutputHelper output)
{
var appFactory = Vdc.Libs.AspNet.Testing.Setup.Factory(
out var client,
out var authorize,
out var identity,
out var faker,
role: $"{VdcSecurity.Role.Management},{VdcSecurity.Role.ManagementAdmin}",
output: output,
setup: services =>
{
}
);
return new Lab(identity, appFactory, client, authorize, faker);
}



I know I could use Mock library to mock the ICloudStackCoreConnectionFactory interface, but as I debug, when the test starts the real "Program.cs" is launched, so is clear we are using the real hosted services and so I don't know how to replace only that interface with the mocked one.


Then I also don't know how to return a DBConnection In-Memory database with data in the "CreateConnection" method.


Any help?


Edit 1: The actual implementation of ICloudStackCoreConnectionFactory
//
// Summary:
// Allow create a connection with the CloudStack database for diferent regions
public sealed class MySqlCloudStackCoreConnectionFactory : ICloudStackCoreConnectionFactory
{
private sealed record QTaskInfo(Region Region, Task CountTask, Task DataTask);

private readonly CloudStackDBOptions _configuration;

private readonly ILogger _logger;

private static readonly ConcurrentDictionary _connections = new ConcurrentDictionary();

public MySqlCloudStackCoreConnectionFactory(IOptions options, ILogger logger)
{
_configuration = options.Value;
_logger = logger;
}

public DbConnection CreateConnection(Region region)
{
return new MySqlConnection(GetConnString(region));
}

public async Task QuerySearchAsync(IRegionRepository regionRepository, string fetchDataQuery, string? countDataQuery = null, Dictionary? columnModelModel = null, Paging? paging = null, ColumnName[]? order = null, string[]? excludedOrder = null, Func? transform = null, object? param = null, Func? filter = null, bool skipDbPaging = false, CancellationToken ct = default(CancellationToken)) where TOut : class
{
string[] excludedOrder2 = excludedOrder;
int total = 0;
List response = new List();
List connections = new List();
StringBuilder stringBuilder = new StringBuilder(fetchDataQuery);
if (order != null && excludedOrder2 != null && excludedOrder2.Length != 0)
{
order = order.Where((ColumnName o) => !excludedOrder2.Contains(o.Name)).ToArray();
}

if (order != null && order.Length != 0)
{
stringBuilder.Append(" ORDER BY");
ColumnName[] array = order;
foreach (ColumnName columnName in array)
{
string value = ((columnModelModel != null) ? columnModelModel[columnName.Name] : columnName.Name);
stringBuilder.Append(' ').Append(value).Append(columnName.Asc ? " ASC," : " DESC,");
}

stringBuilder.Remove(stringBuilder.Length - 1, 1);
}

if (paging != null && !skipDbPaging)
{
stringBuilder.Append(" LIMIT ").Append((paging.Page + 1) * paging.PageSize).Append(';');
}

fetchDataQuery = stringBuilder.ToString();
QTaskInfo[] array2 = ParallelizeTask(fetchDataQuery, countDataQuery, param, connections, await GetOnlineRegion(regionRepository, filter, ct));
Task[] array3 = new Task[2 * array2.Length];
for (int j = 0; j < array3.Length; j += 2)
{
int num = j / 2;
array3[j] = array2[num].CountTask;
array3[j + 1] = array2[num].DataTask;
}

Task.WaitAll(array3, ct);
ValueTask[] array4 = CloseConnection(connections);
for (int k = 0; k < array2.Length; k++)
{
var (region2, task3, task4) = array2[k];
try
{
IEnumerable result2 = task4.Result;
int result3 = task3.Result;
total += result3;
foreach (TDbItem item in result2)
{
TOut val = ((transform != null) ? transform(region2, item) : null) ?? (item as TOut);
if (val != null)
{
response.Add(val);
}
}
}
catch (Exception exception)
{
regionRepository.SetStatus(region2, online: false);
_logger.LogError(exception, "Error request region: {Region}", region2);
}
}

IQueryable result = response.AsQueryable().ApplySearch(paging, order);
_logger.LogInformation("Dispose all connection created");
ValueTask[] array5 = array4;
for (int l = 0; l < array5.Length; l++)
{
ValueTask valueTask = array5[l];
await valueTask;
}

TOut[] array6 = result.ToArray();
return ((countDataQuery == null) ? array6.Length : total, array6);
}

public async Task ExecuteAsync(IRegionRepository regionRepository, string sql, object? @params = null, Func? filter = null, CancellationToken ct = default(CancellationToken))
{
string sql2 = sql;
object params2 = @params;
Region[] array = await GetOnlineRegion(regionRepository, filter, ct);
List connections = new List();
Region[] array2 = new Region[array.Length];
Task[] array3 = new Task[array.Length];
for (int i = 0; i < array3.Length; i++)
{
Region region = array[i];
_logger.LogInformation("Creating connection for: {Region}", region);
DbConnection connection = CreateConnection(region);
Task task = Task.Run(async delegate
{
try
{
_logger.LogDebug("Creating connection for: {Region}", region);
return await connection.ExecuteAsync(sql2, params2);
}
catch (Exception exception2)
{
_logger.LogWarning(exception2, "Error query {Region}", region);
return 0;
}
});
array3[i] = task;
array2[i] = region;
}

Task[] tasks = array3;
Task.WaitAll(tasks, ct);
ValueTask[] array4 = CloseConnection(connections);
int total = 0;
for (int j = 0; j < array3.Length; j++)
{
Task task2 = array3[j];
Region region2 = array2[j];
try
{
int result = task2.Result;
total += result;
}
catch (Exception exception)
{
regionRepository.SetStatus(region2, online: false);
_logger.LogError(exception, "Error request region: {Region}", region2);
}
}

_logger.LogInformation("Dispose all connection created");
ValueTask[] array5 = array4;
for (int k = 0; k < array5.Length; k++)
{
ValueTask valueTask = array5[k];
await valueTask;
}

return total;
}

private static ValueTask[] CloseConnection(List connections)
{
return connections.Select((DbConnection s) => s.DisposeAsync()).ToArray();
}

private string GetConnString(Region region)
{
return region switch
{
Region.Europe => _configuration.Europe,
Region.Asia => _configuration.Asia,
Region.NA => _configuration.NA,
Region.Lab => _configuration.Lab,
_ => throw new NotSupportedException("Region is not supported"),
};
}

private static async Task GetOnlineRegion(IRegionRepository regionRepository, Func? filter = null, CancellationToken ct = default(CancellationToken))
{
Func filter2 = filter;
return (from p in await regionRepository.GetOnlineAsync(ct)
where p != Region.Lab
where filter2?.Invoke(p) ?? true
select p).ToArray();
}

private QTaskInfo[] ParallelizeTask(string fetchDataQuery, string? countDataQuery, object? param, List connections, Region[] onlineRegions)
{
string fetchDataQuery2 = fetchDataQuery;
object param2 = param;
string countDataQuery2 = countDataQuery;
QTaskInfo[] array = new QTaskInfo[onlineRegions.Length];
for (int i = 0; i < array.Length; i++)
{
Region region = onlineRegions[i];
_logger.LogInformation("Creating connection for: {Region}", region);
DbConnection dataConnection = CreateConnection(region);
if (!_connections.GetOrAdd(region, value: false))
{
lock (_connections)
{
if (!_connections.GetValueOrDefault(region))
{
dataConnection.Open();
_connections[region] = true;
}
}
}

Task dataTask = Task.Run(async delegate
{
try
{
_logger.LogDebug("Run Query {Query} with {Args}", fetchDataQuery2, param2);
return await dataConnection.QueryAsync(fetchDataQuery2, param2);
}
catch (Exception exception2)
{
_logger.LogWarning(exception2, "Error query {Region}", region);
return Array.Empty();
}
});
Task countTask;
if (!string.IsNullOrEmpty(countDataQuery2))
{
DbConnection countConnection = CreateConnection(region);
countTask = Task.Run(async delegate
{
try
{
_logger.LogDebug("Run Query {Query} with {Args}", countDataQuery2, param2);
return await countConnection.ExecuteScalarAsync(countDataQuery2, param2);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Error query {Region}", region);
return 0;
}
});
connections.Add(countConnection);
}
else
{
countTask = Task.FromResult(0);
}

connections.Add(dataConnection);
array[i] = new QTaskInfo(region, countTask, dataTask);
}

return array;
}
}



Edit 2: This is our comple "Lab" class we use to initialize all elements needed for the tests (maybe it helps):


Lab.cs:
public sealed class Lab
{

private readonly Faker _faker;
private readonly IdentityInfo _identity;
private readonly WebApplicationFactory _appFactory;
private readonly IApiClient _apiClient;
private readonly AuthorizeOptions _authorize;

private readonly Account[] _data;

private Lab(IdentityInfo identity, WebApplicationFactory appFactory, IApiClient apiClient, AuthorizeOptions authorize, Faker faker)
{
_faker = faker;
_identity = identity;
_appFactory = appFactory;
_apiClient = apiClient;
_authorize = authorize;

_data = [
CreateAccount("Account1", AccountTypes.Managed),
CreateAccount("Account2", AccountTypes.Managed),
CreateAccount("Account3", AccountTypes.Unmanaged),
CreateAccount("Account4", AccountTypes.Internal),
CreateAccount("Account5", AccountTypes.Hybrid),
CreateAccount("Account6", AccountTypes.Hybrid),
CreateAccount("Account7", AccountTypes.Hybrid),
CreateAccount("Account8", AccountTypes.Hybrid)
];
}

public Faker Faker => _faker;
public Account[] Jobs => _data;
public IdentityInfo Identity => _identity;
public IApiClient ApiClient => _apiClient;
public AuthorizeOptions Authorize => _authorize;
public WebApplicationFactory AppFactory => _appFactory;

public async Task InitAsync(IServiceProvider provider, bool useEvents = false)
{
var unitOfWork = provider.GetRequiredService();

//PopulateStatus(provider);
PopulateAccounts(provider, _data);
await unitOfWork.SaveChangesAsync();

EventSend? events = null;
if (useEvents)
Events.Capture(provider, events = new EventSend());

return events;

// ==============================================================================================================
static void PopulateAccounts(IServiceProvider provider, Account[] dataSample)
{
var accountRepository = provider.GetRequiredService();

IEnumerable data = dataSample;
accountRepository.AddRange(data.ToArray());
}
}

///
/// Create a lab with the requirement of this project
///
///
///
///
///
public static Lab Create(ITestOutputHelper output)
{
var appFactory = Vdc.Libs.AspNet.Testing.Setup.Factory(
out var client,
out var authorize,
out var identity,
out var faker,
role: $"{VdcSecurity.Role.Management},{VdcSecurity.Role.ManagementAdmin}",
output: output,
setup: services =>
{
services.AddScoped();
}
);
return new Lab(identity, appFactory, client, authorize, faker);
}

#region Private Methods
private Account CreateAccount(string accountName, AccountTypes typeId)
{
Account account;
var serializer = AppFactory.Services.GetRequiredService();
account = new Account
{
Id = Guid.NewGuid(),
Name = accountName,
CloudStackAccounts = new List(),
TypeId = typeId,
Order = 0
//Deleted = _faker.Date.Past(),
//SLID = _faker.Random.String2(6),
};
return account;
}
#endregion
}
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg
Email ThisBlogThis!Share to XShare to Facebook
Newer Post Older Post Home

0 comments:

Post a Comment

Thanks

Meta

Popular Posts

  • Sitaare Zameen Par Full Movie Review
     Here’s a  complete Vue.js tutorial for beginners to master level , structured in a progressive and simple way. It covers all essential topi...
  • AI foot tracking model
    I am a student doing a graduation project. I urgently need to deal with this model (I am attaching a link). I've never worked with pytho...
  • Laravel Search String
      Laravel Search String is a package by   Loris Leiva   that generates database queries based on one unique string using a simple and custom...
  • Writing and debugging Eloquent queries with Tinkerwell
    In this article, let's look into the options that you can use with Tinkerwell to write and debug Eloquent queries easier. The post Wr...
  • The token request was rejected by the remote server
    error:invalid_granterror_description:The token request was rejected by the remote server.error_uri: https://documentation.openiddict.com/err...

Categories

  • Ajax (26)
  • Bootstrap (30)
  • DBMS (42)
  • HTML (12)
  • HTML5 (45)
  • JavaScript (10)
  • Jquery (34)
  • Jquery UI (2)
  • JqueryUI (32)
  • Laravel (1017)
  • Laravel Tutorials (23)
  • Laravel-Question (6)
  • Magento (9)
  • Magento 2 (95)
  • MariaDB (1)
  • MySql Tutorial (2)
  • PHP-Interview-Questions (3)
  • Php Question (13)
  • Python (36)
  • RDBMS (13)
  • SQL Tutorial (79)
  • Vue.js Tutorial (69)
  • Wordpress (150)
  • Wordpress Theme (3)
  • codeigniter (108)
  • oops (4)
  • php (853)

Social Media Links

  • Follow on Twitter
  • Like on Facebook
  • Subscribe on Youtube
  • Follow on Instagram

Pages

  • Home
  • Contact Us
  • Privacy Policy
  • About us

Blog Archive

  • July (4)
  • September (100)
  • August (50)
  • July (56)
  • June (46)
  • May (59)
  • April (50)
  • March (60)
  • February (42)
  • January (53)
  • December (58)
  • November (61)
  • October (39)
  • September (36)
  • August (36)
  • July (34)
  • June (34)
  • May (36)
  • April (29)
  • March (82)
  • February (1)
  • January (8)
  • December (14)
  • November (41)
  • October (13)
  • September (5)
  • August (48)
  • July (9)
  • June (6)
  • May (119)
  • April (259)
  • March (122)
  • February (368)
  • January (33)
  • October (2)
  • July (11)
  • June (29)
  • May (25)
  • April (168)
  • March (93)
  • February (60)
  • January (28)
  • December (195)
  • November (24)
  • October (40)
  • September (55)
  • August (6)
  • July (48)
  • May (2)
  • January (2)
  • July (6)
  • June (6)
  • February (17)
  • January (69)
  • December (122)
  • November (56)
  • October (92)
  • September (76)
  • August (6)

Loading...

Laravel News

Loading...

Copyright © CoderFunda | Powered by Blogger
Design by Coderfunda | Blogger Theme by Coderfunda | Distributed By Coderfunda