I have plagiarized the below code from the mightysoft docs site on integration testing and adapted it slightly to meet my needs:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly SeedDataClass _seed;
public CustomWebApplicationFactory(SeedDataClass seed)
{
_seed = seed;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.UseEnvironment("Development");
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddSingleton(_seed);
services.AddDbContextPool<GatewayContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
options.EnableSensitiveDataLogging();
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<GatewayContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
var seed = scopedServices.GetRequiredService<SeedDataClass>();
db.Database.EnsureCreated();
try
{
seed.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the database with test messages. Error: {ex.Message}");
}
}
});
}
}
To be used like in a test like:
_client = new CustomWebApplicationFactory<Startup>(new SeedDataClass()).CreateClient();
And this all works but I am looking to add generics to the custom web app factory class and move this code into a nuget package I am working on for internal testing work.
Something like this:
public class CustomWebApplicationFactory<TStartup, TContext>
: WebApplicationFactory<TStartup>
where TStartup : class
where TContext : DbContext
I am stuck on how to provide/inject the SeedDataClass class instance into my new generic custom web app factory.
If you are just trying to adapt a similar constructor to the former implementation of your CustomWebApplicationFactory<TStartup> class
_client = new CustomWebApplicationFactory<Startup>(new SeedDataClass()).CreateClient();
then your new constructor would look like so:
public class CustomWebApplicationFactory<TStartup, TContext> : WebApplicationFactory<TStartup>
where TStartup : class
where TContext : DbContext
{
private readonly SeedDataClass _seed;
public CustomWebApplicationFactory(SeedDataClass seed)
{
if (seed == null) throw new ArgumentNullException(nameof(seed));
_seed = seed;
}
}
and then update your call to the constructor like so
new CustomWebApplicationFactory<Startup, YourDbContext>(new SeedDataClass()).CreateClient();
This is where I was going with this:
Ammended factory:
public class GenericWebApplicationFactory<TStartup, TContext, TSeed>
: WebApplicationFactory<TStartup>
where TStartup : class
where TContext : DbContext
where TSeed : class, ISeedDataClass
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.UseEnvironment("Development");
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddSingleton<ISeedDataClass,TSeed >();
services.AddDbContextPool<TContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
options.EnableSensitiveDataLogging();
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<TContext>();
var logger = scopedServices.GetRequiredService<ILogger<GenericWebApplicationFactory<TStartup, TContext, TSeed>>>();
var seeder = scopedServices.GetRequiredService<ISeedDataClass>();
db.Database.EnsureCreated();
try
{
seeder.InitializeDbForTests();
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the database with test messages. Error: {ex.Message}");
}
}
});
}
}
Ammended usage:
_client = new GenericWebApplicationFactory<Startup, GatewayContext, SeedDataClass>().CreateClient();
With example seed class:
public interface ISeedDataClass
{
void InitializeDbForTests();
}
public class SeedDataClass : ISeedDataClass
{
private readonly GatewayContext _db;
public SeedDataClass(GatewayContext db)
{
_db = db;
}
public void InitializeDbForTests()
{
_db.Users.AddRange(
// add some users here
);
_db.SaveChanges(true);
}
}
Now, I can seed the in memory database however I see fit, per project where it is employed and my GenericWebApplicationFactory can now be pushed into a helper lib/nuget package which be re-used in other projects.
Related
I was using my unit tests on .Net5(or lower) with DependencyResolverHelper like this below. This is my base test class
public abstract class BaseTestClass
{
protected DependencyResolverHelper _serviceProvider;
public BaseTestClass()
{
var webHost = WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
.Build();
_serviceProvider = new DependencyResolverHelper(webHost);
}
}
and my DependencyResolverHelper
public class DependencyResolverHelper
{
private readonly IWebHost _webHost;
/// <inheritdoc />
public DependencyResolverHelper(IWebHost webHost) => _webHost = webHost;
public T GetService<T>()
{
var serviceScope = _webHost.Services.CreateScope();
var services = serviceScope.ServiceProvider;
try
{
var scopedService = services.GetRequiredService<T>();
return scopedService;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
but im bit confused with new .NET 6 Dependency things. Does anyone tried it without startup class?
I tried to change it with
WebApplicationBuilder
but it gave the
No service for type 'MinimalAPI.Services.TokenService.ITokenService' has been registered. error.
Just Because this part of codes .UseStartup<Startup>() called startup class and registed the services for you,If you try with WebApplicationBuilder in .net 6,You have to regist the services yourself,
I tried as below:
public void Test1()
{
var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { });
builder.Services.AddSingleton<ISomeService,SomeService>();
var app = builder.Build();
var serviceProvider = new DependencyResolverHelper(app);
var someservice = serviceProvider.GetService<ISomeService>();
Assert.Equal(typeof(SomeService), someservice.GetType());
}
DependencyResolverHelper class:
public class DependencyResolverHelper
{
private readonly WebApplication _app;
/// <inheritdoc />
public DependencyResolverHelper(WebApplication app) => _app = app;
public T GetService<T>()
{
var serviceScope = _app.Services.CreateScope();
var services = serviceScope.ServiceProvider;
try
{
var scopedService = services.GetRequiredService<T>();
return scopedService;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
Result:
I'm facing a strange issue since I created a view component in my app.
All my integration tests using an HttpClient start failing with response code "Internal Server Error".
Here the test configuration :
public class BaseTest<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly bool _hasUser;
private readonly HttpClient _client;
protected BaseTest(bool hasUser = false)
{
_hasUser = hasUser;
_client = CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
}
protected async Task<HttpResponseMessage> GetPageByPath(string path)
{
return await _client.GetAsync(path);
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
if (_hasUser)
{
services.AddScoped<IAuthenticationService, AuthenticationServiceStub>();
services.AddSingleton<IStartupFilter, FakeUserFilter>();
services.AddMvc(options =>
{
options.Filters.Add(new AllowAnonymousFilter());
options.Filters.Add(new AuthenticatedAttribute());
});
}
});
builder.ConfigureServices(services =>
{
// Create a new service provider.
ServiceProvider serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var logger = scopedServices
.GetRequiredService<ILogger<BaseTest<TStartup>>>();
}
});
}
}
}
And a usage example :
public class BasicTest : BaseTest<Startup>
{
public BasicTest() : base()
{
}
[Theory]
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/Users/SignOut")]
[Trait("Category", "Integration")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Act
var response = await GetPageByPath(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
If you need the component code let me know.
I already rollback code to check when this start happening, and it's start after the commit with the new Component called in several pages.
Tool Versions
asp.net core 2.2
Autofac 4.9.2
Problem
I'm trying to inject a logger instance into a class that is already configured for dependency inject by Autofac
Setup
I have a class that queries a database. The class has a connection string injected into it by Autofac. The below setup works.
Class without the Logger injected:
public class ItemQueries
: IItemQueries
{
private readonly string _connectionString = default(string);
public ItemQueries(string connectionString)
{
_connectionString = !string.IsNullOrWhiteSpace(connectionString) ? connectionString : throw new ArgumentNullException(nameof(connectionString));
}
public async Task<ItemViewModel> GetItemAsync(int id)
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
var result = await connection.QueryAsync<dynamic>(
#"SELECT *
FROM dbo.items
WHERE Id=#id"
, new { id }
);
if (result.AsList().Count == 0)
{
throw new KeyNotFoundException();
}
return MapItem(result.FirstOrDefault());
}
}
private ItemViewModel MapItem(dynamic result)
{
return new ItemViewModel()
{
Id = result.Id,
Name = result.Name
};
}
}
This is how I've registered the class with Autofac:
public class ItemModule
: Autofac.Module
{
public string QueriesConnectionString { get; }
public ItemModule(string queriesConnectionString)
{
QueriesConnectionString = queriesConnectionString;
}
protected override void Load(ContainerBuilder builder)
{
builder.Register(c => new ItemQueries(QueriesConnectionString)) // <-- this breaks when I make the below changes because it's looking for an instance of ILogger<ItemQueries>
.As<IItemQueries>()
.InstancePerLifetimeScope();
}
}
However, I want to inject my Logger instance into the ItemQueries class, so I injected it in the constructor as so:
private readonly ILogger<ItemQueries> _logger;
private readonly string _connectionString = default(string);
public ItemQueries(ILogger<ItemQueries> logger, string connectionString)
{
_logger = logger;
_connectionString = !string.IsNullOrWhiteSpace(connectionString) ? connectionString : throw new ArgumentNullException(nameof(connectionString));
}
But that breaks the registration in my Autofac module. How do I tell Autofac that the ItemQueries class requires the Logger instance to be injected?
I thought Autofac would be able to workout that the ItemQueries class needs the ILogger<> instance to be inject (you know, as if by magic!)
You can resolve things in lambda registrations. That's what the context parameter in the lambda is for.
builder
.Register(c =>
new ItemQueries(QueriesConnectionString, c.Resolve<ILogger<ItemQueries>>()))
.As<IItemQueries>()
.InstancePerLifetimeScope();
You need to use the feature "Factory" from Autofac.
Here is the setup of your solution with Factory
private readonly ILogger<ItemQueries> _logger;
private readonly string _connectionString = default(string);
public delegate ItemQueries Factory(string connectionString); // --> here, you create the delegate factory
// connectionString needs to be the first parameter
public ItemQueries(string connectionString, ILogger<ItemQueries> logger)
{
_logger = logger;
_connectionString = !string.IsNullOrWhiteSpace(connectionString) ? connectionString : throw new ArgumentNullException(nameof(connectionString));
}
And the module setup should be like this:
public class ItemModule: Autofac.Module
{
public string QueriesConnectionString { get; }
public ItemModule(string queriesConnectionString)
{
QueriesConnectionString = queriesConnectionString;
}
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ItemQueries>(); // --> not sure if needed
builder.Register(c =>
{
var factory = c.Resolve<ItemQueries.Factory>();
return factory.Invoke(QueriesConnectionString);
})
.As<IItemQueries>()
.InstancePerLifetimeScope();
}
}
I have a WebApi done in Core.net 2.0, with UOW , and automapper.
Everything is working fine, but now I want to implement Unit Test with Nunit, and I have this error of automapper
Message: System.InvalidOperationException : Mapper not initialized.
Call Initialize with appropriate configuration. If you are trying to
use mapper instances through a container or otherwise, make sure you
do not have any calls to the static Mapper.Map methods, and if you're
using ProjectTo or UseAsDataSource extension methods, make sure you
pass in the appropriate IConfigurationProvider instance.
How can I solve this. Thanks in advance .
Jolynice
Class AutoMapperProfile.cs
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<Cars, CarsDTO>()
.ReverseMap();
}
}
class Startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
//removed configurations
// Add cors
services.AddCors();
// Add framework services.
services.AddMvc();
Mapper.Initialize(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
// Repositories
services.AddScoped<IUnitOfWork, HttpUnitOfWork>();
services.AddScoped<IAccountManager, AccountManager>();
}
}
class carsController.cs
[Authorize]
[Route("api/[controller]")]
public class CarsController : Controller
{
private IUnitOfWork _unitOfWork;
readonly ILogger _logger;
private readonly IAccountManager _accountManager;
public CarsController(
IUnitOfWork unitOfWork,
ILogger<CarsController> logger,
IAccountManager accountManager)
{
_unitOfWork = unitOfWork;
_logger = logger;
_accountManager = accountManager;
}
[HttpGet]
public IActionResult GetAll()
{
var allCars = _unitOfWork.CarsRepository.GetAllCarsData();
if (allCars == null)
{
return NotFound();
}
return Ok(Mapper.Map<IEnumerable<CarsDTO>>(allCars));
}
and this is my unit test
[TestFixture]
public class CarsControllerTest
{
#region private variables
List<Cars> cars = new List<Cars>();
CarsController _carsController = null;
IUnitOfWork _unitOfWork;
ICarsRepository _carsRepository;
#endregion
[SetUp]
public void SetUp()
{
cars = new List<Cars>
{
new Cars
{
Alias = "406Moq",
BrandId = 1,
ModelId = 1,
Plate = "00-00-01",
AltranVehicle = 0,
DefaultCar = 0,
Active = 1,
ColorId = 1
}
};
}
[Test]
public void GetAllCarsControllerTest()
{
//Arrange
_carsRepository = SetupCarsRepository();
var unityOfWork = new Mock<IUnitOfWork>();
var _logger = new Mock<ILogger<CarsController>>();
var accountManager = new Mock<IAccountManager>();
unityOfWork.SetupGet(c => c.CarsRepository).Returns(_carsRepository);
_unitOfWork = unityOfWork.Object;
_carsController = new CarsController(_unitOfWork, _logger.Object, accountManager.Object);
//Act
var carsResult = _carsController.GetAll();
//Assert
carsResult.StatusCode.Should().Be(HttpStatusCode.OK);
}
private ICarsRepository SetupCarsRepository()
{
//initialize repository
var mockRepo = new Mock<ICarsRepository>(MockBehavior.Default);
//Setup mocking behavior
mockRepo.Setup(c => c.GetAllCarsData()).Returns(cars);
return mockRepo.Object;
}
//Cleanup
[TearDown]
public void TearDown()
{
cars = null;
}
}
}
You are missing initialization of your mapper in your unit test. The following initializes the mapper in the CarsControllerTest class constructor.
[TestFixture]
public class CarsControllerTest
{
public CarsControllerTest()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile<AutoMapperProfile>();
});
}
}
I have a console application that Autofac DI is used to inject data and service layer from web application project.
here is the setup on console application:
public static class ContainerConfig
{
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbFactory>().As<IDbFactory>();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<DataRepository>().As<IDataRepository>();
builder.RegisterType<DataService>().As<IDataService>();
return builder.Build();
}
}
public interface IApplication
{
void Run();
}
public class Application : IApplication
{
private readonly IDataService _dataService;
public Application(IDataService dataService)
{
_dataService = dataService;
}
public void Run()
{
var data = _dataService.GetDataById(1);
var task = new TestTask("test");
data.AddTask(task);
_dataService.Update(data);
_dataService.SaveChanges();
}
}
main Program class:
class Program
{
static void Main(string[] args)
{
var container = ContainerConfig.Configure();
using (var scope = container.BeginLifetimeScope())
{
var app = scope.Resolve<IApplication>();
app.Run();
}
}
}
When the application is run loading the data works fine. However, saving a new entry does not seem to do the work.
However, when I remove DI and use simple class initializing in the Run method as below the save works fine:
IDbFactory dbFactory = new DbFactory();
IDataRepository dataRepository = new DataRepository(dbFactory);
var unitOfWork = new UnitOfWork(dbFactory);
IDataService service = new DataService(dataRepository, unitOfWork);
var data = service.GetDataById(1);
var task = new TestTask("test");
data.AddTask(task);
service.Update(data);
service.SaveChanges();
Am I missing something while I setup the autofac? It seems to access the data fine but when it comes to save it does not save the data. I debugged to see any issue but the program runs fine with no error. How can I debug this sort of issues to find more details?
Updated
public interface IDataService
{
void Add(TestTask task);
void SaveChanges();
}
public class DataService : IDataService
{
private readonly IDataRepository _dataRepository;
private readonly IUnitOfWork _unitOfWork;
public DataService(IDataRepository dataRepository, IUnitOfWork unitOfWork)
{
_dataRepository = dataRepository;
_unitOfWork = unitOfWork;
}
public void Add(TestTask task)
{
_dataRepository.Add(task);
}
public void SaveChanges()
{
_unitOfWork.Commit();
}
}
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory _dbFactory;
private ApplicationDbContext _dbContext;
public UnitOfWork(IDbFactory dbFactory)
{
this._dbFactory = dbFactory;
}
public ApplicationDbContext DbContext => _dbContext ?? (_dbContext = _dbFactory.Init());
public void Commit()
{
DbContext.Commit();
}
}
After reading autofac scopes here
I found out that default scope is Instance Per Dependency. Which means that a unique instance will be returned from each request for a service. DbFactory should be for InstancePerLifetimeScope.
So changing configuration below fixes the issue:
public static class ContainerConfig
{
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
builder.RegisterType<Application>().As<IApplication>();
builder.RegisterType<DataRepository>().As<IDataRepository>();
builder.RegisterType<DataService>().As<IDataService>();
return builder.Build();
}
}