I successfully injected dependencies using Moq in my unit test project. But for the integration testI would like to interact with the database. So I canot fake the repositories/ dependencies. I am having trouble how to achieve such thing in seperate class library introduced for integration testing.
I would like to do something like this (data should come from database):
public class CountryServiceIntegrationTest
{
private ICountryService countryService;
public CountryServiceIntegrationTest(ICountryService _countryService)
{
countryService = _countryService;
}
#endregion
[Fact]
public void Should_Return_ListOf_Countries()
{
//Act
var myList = countryService.GetList("A");
//Assert
Assert.True(myList.Count > 0);
}
}
My CountryService Class:
public class CountryService : ICountryService
{
// Note: Have to use Core.Domain.Country because of the namespace has Quantum.Service.Country
protected IRepository<Core.Domain.Country> _countryRepository;
protected IRepository<Core.Domain.State> _stateRepository;
protected IRepository<Core.Domain.City> _cityRepository;
public CountryService(IRepository<Core.Domain.Country> countryRepository, IRepository<Core.Domain.State> stateRepository, IRepository<Core.Domain.City> cityRepository)
{
_countryRepository = countryRepository;
_stateRepository = stateRepository;
_cityRepository = cityRepository;
}
public IList<CountryViewModel> GetList(string name)
{
var query = _countryRepository.Table.AsQueryable();
if (string.IsNullOrEmpty(name) == false)
{
query = query.Where(i => i.CountryName.StartsWith(name));
}
return query.Select(i => new CountryViewModel()
{
CountryCode = i.CountryCode,
CountryName = i.CountryName,
Currency = i.Currency,
CurrencyName = i.CurrencyName,
CurrencySymbol = i.CurrencySymbol,
TelephoneCountryCode = i.TelephoneCountryCode,
UnitOfMeasure = i.UnitOfMeasure
}).ToList();
} }
Well I have separate IOC class library project where dependencies are registered. This is then registered in the Startup.cs class. Since Startup.cs class isn't invoked during the tests, the dependencies aren't injected. So how can I solve this problem?
------UPDATED As per guidelines found in official documentation here -----
Well now: I followed this link and did as per it. It seems to me that Startup class was called which also calls the ConfigureDependency.RegisterDependencies(..).
Test Class:
public CountryServiceIntegrationTest()
{
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnHelloWorld()
{
//Act
var response = await _client.GetAsync("/home/Test");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
//Assert
Assert.Equal("test", responseString);
}
Startup.ConfigureServices() :
public IConfigurationRoot Configuration { get; }
//gets called in the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//services.AddSingleton<ILogUserActivityService, LogUserActivityService>();
services.AddSingleton<ActivityLog>();
// Add framework services.
services.AddMvc();
// Register Database Connection String
var connectionSetting = new ConnectionSetting(Configuration["Data:ConnectionStrings:DefaultConnection"]);
services.AddSingleton<IConnectionSetting>(connectionSetting);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
// Fill other dependencies
var configureDependency = new ConfigureDependency();
configureDependency.RegisterDependencies(services, connectionSetting);
}
ConfigureDependency.RegisterDependency(..):
public class ConfigureDependency
{
public IDatabaseFactory DatabaseFactory { get; set; }
public void RegisterDependencies(IServiceCollection services, IConnectionSetting connectionSetting)
{
services.AddDbContext<QuantumDbContext>(options => options.UseSqlServer(connectionSetting.Get()));
services.AddTransient<IDatabaseFactory, DatabaseFactory>();
services.AddTransient<IDbContext, TestDbContext>();
services.AddTransient<IDbContext, QuantumDbContext>();
..................................................................
...........service n repositories are registered here..............
}
}
But now what happens is I get this error:
Since Startup.cs is invoked which then calls the ConfigureDependency class, doesn't it mean that parameters(services, connectionSetting) shall be passed automatically. This is (ConfigureDependency.RegisterDependencies(..)) where I am getting an error.
It's an ArgumentNullException
in the useSqlServer
method:
It seems that connectionSetting.Get()
returns null
.
In the following code
var connectionSetting = new ConnectionSetting(Configuration["Data:ConnectionStrings:DefaultConnection"]);
services.AddSingleton<IConnectionSetting>(connectionSetting);
It suggests that ConnectionSetting
implements the interface IConnectionSetting
so why didn't you use directly the instance instead of calling Get()
on it ?
Like below:
services.AddDbContext<QuantumDbContext>(options => options.UseSqlServer(connectionSetting))
Additional Remarks:
It really depends on what you do mean by integration test. It could refers to:
It's often better to have several layers test-covered before trying to do the Big Bang testing... but it's a matter of tradeoff between time and quality.
In order to make the not so high level integration tests possible/easy to write:
You should not share the same database environment between your tests and your production code (so not the same connection string).
you shouldn't use Startup as it's designed to mimics the whole website on a test server.
Registration and Resolution of services should be splitted up in some coherent specific classes to make integration tests on specific parts easier.