← → or space · progress saves for Continue on the roadmap
Goal
Pass dependencies in through constructors (or factories) so callers and tests control implementations.
Step 1 - Before (hidden dependency)
class AuthService {
Future<bool> login(String email, String password) async {
final client = HttpApiClient();
final raw = await client.post('/login', body: {'email': email, 'password': password});
return raw.status == 200;
}
}- Hard to fake
HttpApiClientin a unit test without network hooks.
Step 2 - After (injected)
class AuthService {
final ApiClient _client;
AuthService(this._client);
Future<bool> login(String email, String password) async {
final raw = await _client.post('/login', body: {'email': email, 'password': password});
return raw.status == 200;
}
}
abstract class ApiClient {
Future<HttpResponse> post(String path, {Map<String, String>? body});
}
class HttpResponse {
final int status;
HttpResponse(this.status);
}- Tests pass a
FakeApiClientthat returns cannedHttpResponsevalues.
Good habits
- Prefer
finalfields set once in the constructor. - Avoid optional “inject or construct internally” parameters; pick one style per type.
Practice tasks
- Convert one static
DateTime.now()usage to an injectableClockinterface withDateTime now()for tests. - List constructors in a feature and mark which parameters are infrastructure vs pure config.