@@ -58,28 +58,29 @@ fails, you get all the tested application logs in the test output.
5858
5959### ` ConfigureAppServices `
6060
61- Override dependency injection services of the tested application.
62-
63- If your test code and the tested application need to access the same
64- instance of a service, you need to inject this instance using a
65- Singleton lifetime. Accessing the DI container outside of the client
66- call will return an unscoped provider as scopes are created and disposed
67- by the framework for each request.
61+ This method allows to override dependency injection services of the
62+ tested application. This is useful to inject class doubles. Whenever the
63+ tested application will try to resolve a service declared here, they will
64+ get instances of the ` FakeService ` instead of the original types:
6865
6966``` cs
70- // Override a service with custom implementation in the tested app
67+ // Override services with custom implementations
7168protected override void ConfigureAppServices (IServiceCollection services )
72- => services .AddSingleton <IMyService , FakeService >();
73-
74- [Fact ]
75- public async Task OnTest ()
7669{
77- // Access the injected service from the test code
78- var service = Services . GetRequiredService < IMyService >();
79- service . SetValue ( expected );
70+ services . AddScoped < IMyScopedService , FakeService >();
71+ services . AddSingleton < IMySingletonService , FakeService >();
72+ services . AddTransient < IMyTransientService , FakeService >( );
8073}
8174```
8275
76+ You can also overide the lifetime of the service:
77+
78+ ``` cs
79+ // Override a scoped service with a singleton object
80+ protected override void ConfigureAppServices (IServiceCollection services )
81+ => services .AddSingleton <IMyScopedService >(new FakeInstance ());
82+ ```
83+
8384## Accessing the tested application
8485
8586### ` Client `
@@ -104,14 +105,47 @@ currently available to the tested application.
104105
105106### ` Services `
106107
107- This property grants you access to the DI service provider of the tested
108- application.
108+ This property grants you an access to the DI service container of the
109+ tested application:
110+
111+ ``` cs
112+ [Fact ] public void Test ()
113+ {
114+ var service = Services .GetService <IMyService >();
115+ }
116+ ```
117+
118+ Scopes are handled by the framework at the request level, so the test
119+ and the tested application cannot share a common scope. This means that
120+ if you resolve a scoped service from the test, you will get a different
121+ instance than the one used in the tested application.
122+
123+ ``` cs
124+ [Fact ] public void Test ()
125+ {
126+ // these two instances are not the same !
127+ var expected = Services .GetService <IMyScopedService >().GetHashCode ();
128+ var actual = await Client .GetScopedServiceHashCode ();
129+ actual .Should ().Be (expected ); // FAIL
130+ }
131+ ```
132+
133+ If you need the test class and the tested application to share object
134+ instances, you need to override their lifetime to singleton. Beware
135+ of impacts due to the usage of a singleton object accross multiple
136+ request.
109137
110- If your test code and the tested application need to access the same
111- instance of a service, you need to inject this instance using a
112- Singleton lifetime. Accessing the DI container outside of the client
113- call will return an unscoped provider as scopes are created and disposed
114- by the framework for each request.
138+ ``` cs
139+ // Override a scoped service with a singleton instance
140+ protected override void ConfigureAppServices (IServiceCollection services )
141+ => services .AddSingleton <IMyScopedService , FakeService >();
142+
143+ [Fact ] public void Test ()
144+ {
145+ var expected = Services .GetService <IMyScopedService >().GetHashCode ();
146+ var actual = await Client .GetScopedServiceHashCode (); // SUCCESS
147+ }
148+ ```
115149
116150## Extending the behavior of the test class
117151
@@ -138,8 +172,8 @@ that only uses the entrypoint.
138172You will need to override the abstract ` ConfigureDbContext ` method to
139173tell the dependency injection library how to configure your context. A
140174context instance will be generated per test and injected in your target
141- app as a singleton . You can access the same context instance in your
142- test through the ` Database ` property.
175+ app. You can access the same context instance in your test through the
176+ ` Database ` property.
143177
144178``` cs
145179protected override void ConfigureDbContext (DbContextOptionsBuilder builder )
@@ -193,23 +227,37 @@ protected override void ConfigureDbContext(DbContextOptionsBuilder builder)
193227
194228This beahvior WILL drop your database after each test !
195229
196- ### Cleaning the ChangeTracker
230+ ### Database context lifetime
231+
232+ By default, the library injects the DbContext as scoped service. If you
233+ run the test using a transaction isolation level, the test code needs to
234+ access the context instance to start and rollback the transactions.
235+ In that case you need to tell the runtime to inject the DbContext as a
236+ singleton service trough the ` DatabaseLifetime ` property:
237+
238+ ``` cs
239+ protected override IDatabaseTestStrategy < Context > DatabaseTestStrategy
240+ => IDatabaseTestStrategy <Context >.Transaction ;
241+
242+ protected override ServiceLifetime DatabaseLifetime
243+ => ServiceLifetime .Singleton ;
244+
245+ protected override void ConfigureDbContext (DbContextOptionsBuilder builder )
246+ => builder .UseSqlite ($" Data Source={Guid .NewGuid ()}.sqlite" );
247+ ```
197248
198- When running normally, dotnet web applications will use a different scoped
199- instance of the DbContext for each HTTP request. The test library uses a
200- singleton instance, otherwise it would not be possible to access the same
201- instance from within the test context. As a consequence, any call to the
249+ When running the tests with a singleton DbContext, any call to the
202250DbContext during the arrange phase (including calls to the HttpClient) might
203251clogger the Change Tracker with existing entities. In this situation the
204252DbContext's ChangeTracker might not be empty when the system under test is
205253called. This may in turn cause attaching entities to fail, whereas it would
206- have worked in a real request.
254+ have worked in a real request. In such case, the test writer should call
255+ ` Database.ChangeTracker.Clear() ` at the end of the arrange phase.
207256
208257The most common pattern for this is when arranging some entities in the
209258database then calling an update entity operation.
210259
211- In such case, the test writer should call ` Database.ChangeTracker.Clear() `
212- at the end of the arrange phase.
260+ This operation is not needed if the DatabaseLifetime is set to scoped.
213261
214262## OpenTelemetry integration
215263
0 commit comments