Every team you work in will have their own understanding of
integration tests, I tried to explain this better in my post on Testing Strategies
A general explanation of
integration tests could be as follows:
Code that invokes a unit of work that crosses project boundaries, uses actual external dependencies, and/or validates many different aspects about the code under test.
This can visually be understood as
That said, the integration can also be seen as the integration of units of work with-in your application. These units of work could be services or command/queries. Depending on what these units actually do they could be crossing project boundaries.
Unit bmay implement a http client that interacts with an external API - thats outside of your projects boundary. So the above image reflects this.
Unit bmay implement some math calculations like
return 42 + 0;- thats inside your projects boundary but it still integrates with
I’ve worked in teams where
Integration Tests are re-used, the external dependencies are mocked out with another appsettings file. This is in my opinion then a
Component Test, I view the two units above
unit a and
unit b together as a component.
Depending on what the test actually does it could be an E2E test (End to End)
Naming is tricky, however as a team we decided on the terminology
Mock Integration Test as a
component in front end development means something else. As our app was .Net the example appsettings name was
appsettings.Mock.json - its important to define the terms as a team and then stick to them.
It makes sense to break the tests down but still group them by
Features - these should be already grouped by your controllers. By design controllers are allowed to do too much, you can add as many endpoints in them as you like - thats a different topic, see MVC Controllers are Dinosaurs - Embrace API Endpoints. For my features examples however grouping responsability makes integration testing by feature easier.
Consider these controllers,
Artists has simple CRUD operations and so does
Songs, these operations are abstracted away and then injected as an interface, using the
Controllers folder is simply following convention over configuration.
I would understand this as one
Artist has many
The integration tests could then be created as
Features/Songs, understandably there could be overlap if a Artist is persisted with some songs - ideally the link would just be the foreign key and not the whole implementation so design considerations need to thought out.
The abstractions the controller instantiates though dependency injection would still be injected by the applications framework, their behavior also doesnt change however the configuration (appsettings in .Net) can be changed by environment. This means the application needs to have the same
appsettings values as the integration tests.
When to use: when you want to create a single test context and share it among tests in several test classes, and have it cleaned up after all the tests in the test classes have finished. - xunit.net/docs/shared-context
The tests can use something called a
Fixture to instantiate their dependencys. One or more fixtures are then injected into our tests. So below the
CollectionFixture is really a base class to share context between tests.
public class CollectionFixture : IAsyncLifetime
IAsyncLifetime to call into the services to dispose of resources. If you use
InitializeAsync here to seed any data you must remember its for all tests. If you want to seed data just for a subset then see
Class Fixtures below.
public Task DisposeAsync()
To then allow the
CollectionFixture to be instanciated and managed we need to create a
Test classes that want to use
CollectionFixture should be decorated with
[Collection("Shared Collection Fixture")]. Their constructor parameters can then include
CollectionFixture collectionFixture. The facts can then access
FooServiceClient should inherit/implement
IDisposable to dispose of any clients it may have. This is called from the fixtures
DisposeAsync method to ensure all state is disposed of.
GC.SuppressFinalize(this) will prevent derived types that introduce a finalizer from needing to re-implement IDisposable to call it. See CA1816: Call GC.SuppressFinalize correctly
Alternatively the client
FooServiceClient could use the sealed keyword to prevent inheritance.
public class FooServiceClient : IDisposable
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished. - xunit.net/docs/shared-context
Create the fixture the same as above but it will not need the
public class FindStuffFixture : IAsyncLifetime
Create the tests and inherit/implement
IClassFixture<T>. Note that
[Collection("Shared Collection Fixture")] was only added to allow the test access to
CollectionFixture which is where the clients are constructed.