I have build a WebAPI and apart from my tests running on Postman I would like to implement some Integration/Unit tests.
Now my business logic is very thin, most of the time its more of CRUD actions, therefore I wanted to start with testing my Controllers.
I have a basic setup. Repository pattern (interfaces), Services (business logic) and Controllers. The flow goes Controller (DI Service) -> Service (DI Repo) -> Repo Action!
So what I did was override my Startup file to change into a in memory database and the rest should be fine (I would assume) Services are added, repos are added and now I am pointing into a in memory DB which is fine for my basic testing.
namespace API.UnitTests
{
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env)
: base(env)
{
}
public void ConfigureTestServices(IServiceCollection services)
{
base.ConfigureServices(services);
//services.Replace<IService, IMockedService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
base.Configure(app, env, loggerFactory);
}
public override void SetUpDataBase(IServiceCollection services)
{
var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
services
.AddEntityFrameworkSqlite()
.AddDbContext<ApplicationDbContext>(
options => options.UseSqlite(connection)
);
}
}
}
I wrote my first test, but the DatasourceService is not there:
The following constructor parameters did not have matching fixture data: DatasourceService datasourceService
namespace API.UnitTests
{
public class DatasourceControllerTest
{
private readonly DatasourceService _datasourceService;
public DatasourceControllerTest(DatasourceService datasourceService)
{
_datasourceService = datasourceService;
}
[Xunit.Theory,
InlineData(1)]
public void GetAll(int companyFk) {
Assert.NotEmpty(_datasourceService.GetAll(companyFk));
}
}
}
What am I missing?
The xUnit.net test runner that we've been using supports . NET Core 1.0 or later, . NET 5.0 or later, and . NET Framework 4.5.
xUnit.net does not require an attribute for a test class; it looks for all test methods in all public (exported) classes in the assembly. Set the Skip parameter on the [Fact] attribute to temporarily skip a test.
You can't use dependency injection on test classes. You can only let xunit inject special fixtures via constructor (see docs).
For Integration Testing you want to use the TestServer
class from Microsoft.AspNetCore.TestHost
package and a separate Startup.cs
class (easier to setup configuration than inheritance imho).
public class TestStartup : Startup
{
public TestStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureTestServices(IServiceCollection services)
{
services.Replace(ServiceDescriptor.Scoped<IService, MockedService>());
services.AddEntityFrameworkSqlite()
.AddDbContext<ApplicationDbContext>(
options => options.UseSqlite(connection)
);
}
public void Configure(IApplicationBuilder app)
{
// your usual registrations there
}
}
In your unit test project, you need to create an instance of the TestServer
and perform the test.
public class DatasourceControllerTest
{
private readonly TestServer _server;
private readonly HttpClient _client;
public DatasourceControllerTest()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>());
_client = _server.CreateClient();
}
[Xunit.Theory,
InlineData(1)]
public async Task GetAll(int companyFk) {
// Act
var response = await _client.GetAsync($"/api/datasource/{companyFk}");
// expected result from rest service
var expected = @"[{""data"":""value1"", ""data2"":""value2""}]";
// Assert
// This makes sure, you return a success http code back in case of 4xx status codes
// or exceptions (5xx codes) it throws an exception
response.EnsureSuccessStatusCode();
var resultString = await response.Content.ReadAsStringAsync();
Assert.Equals(resultString, expectedString);
}
}
Now, when you call operations which write to the database, you can also check if the data is really written to the database:
[Xunit.Theory,
InlineData(1)]
public async Task GetAll(int companyFk) {
// Act
var response = await _client.DeleteAsync($"/api/datasource/{companyFk}");
// expected result from rest service
// Assert
response.EnsureSuccessStatusCode();
// now check if its really gone in the database. For this you need an instance
// of the in memory Sqlite DB. TestServer has a property Host, which is an IWebHost
// and it has a property Services which is the IoC container
var provider = _server.Host.Services;
var dbContext = provider.GetRequiredService<ApplicationDbContext>();
var result = await dbContext.YourTable.Where(entity => entity.Id == companyFk).Any();
// if it was deleted, the query should result in false
Assert.False(result);
}
Now you can use Xunit.DependencyInjection in your tests.
namespace Your.Test.Project
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDependency, DependencyClass>();
}
}
}
your DI-classes:
public interface IDependency
{
int Value { get; }
}
internal class DependencyClass : IDependency
{
public int Value => 1;
}
and XUnit-test:
public class MyAwesomeTests
{
private readonly IDependency _d;
public MyAwesomeTests(IDependency d) => _d = d;
[Fact]
public void AssertThatWeDoStuff()
{
Assert.Equal(1, _d.Value);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With