Now that we have the Repository implementation in place, we're going to implement its dependencies, starting with the NetworkInfo class used for finding out if the device is currently connected to a network. This part is where we will finally do test-driven development with 3rd party packages, which means we're going to mock 3rd party classes.
As always, we already know how the NetworkInfo's interface looks like, because we've defined its contract first. It has a single property called isConnected.
network_info.dart
abstract class NetworkInfo {
Future<bool> get isConnected;
}
Switching Packages
In the second part I told you that we will use the connectivity package to find out about the network status. While that package is useful for determining if the device is running on mobile data or on WiFi, I found out it's not that great to check for an actual Internet access. As it writes right on the package page:
Since we cannot rely solely on the information which the platform (Android/iOS) provides, what can we rely on instead? On actually connecting to something, of course! The data_connection_checker package is just perfect for that.
pubspec.yaml
dependencies:
# Swap the connectivity package for this
data_connection_checker: ^0.3.4
It opens a socket to certain addresses and determines the real connection status based on whether it can actually connect. Also, it's completely platform independent - it can work on the web too!
Now that we know we won't use the connectivity package because getting connection info from the platform isn't reliable, it's only wise to rename the folder core/platform to core/network. We'll also need to fix the imports on the Repository implementation and test.
Implementation
Let's first create a test file at a mirrored location as usual. Although there won't be much logic to perform, it's important not to let go of TDD even in such cases. Bugs can be hidden even in a seemingly innocuous code.
The NetworkInfo class should take in a DataConnectionChecker instance into its constructor and, of course, we're going to create a mock. The actual test of the isConnected property will be a bit different from the tests we've written until now...
network_info_test.dart
import 'package:clean_architecture_tdd_prep/core/network/network_info.dart';
import 'package:data_connection_checker/data_connection_checker.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
class MockDataConnectionChecker extends Mock implements DataConnectionChecker {}
void main() {
NetworkInfoImpl networkInfo;
MockDataConnectionChecker mockDataConnectionChecker;
setUp(() {
mockDataConnectionChecker = MockDataConnectionChecker();
networkInfo = NetworkInfoImpl(mockDataConnectionChecker);
});
group('isConnected', () {
test(
'should forward the call to DataConnectionChecker.hasConnection',
() async {
// arrange
final tHasConnectionFuture = Future.value(true);
when(mockDataConnectionChecker.hasConnection)
.thenAnswer((_) => tHasConnectionFuture);
// act
// NOTICE: We're NOT awaiting the result
final result = networkInfo.isConnected;
// assert
verify(mockDataConnectionChecker.hasConnection);
// Utilizing Dart's default referential equality.
// Only references to the same object are equal.
expect(result, tHasConnectionFuture);
},
);
});
}
Calling NetworkInfo().isConnected is really only a nickname for calling DataConnectionChecker().hasConnection. We're simply hiding the 3rd party library behind an interface of our own class. We can check if the call to the property is "forwarded" by checking if the Future object returned by isConnected is exactly the same as the one returned by hasConnection.
The implementation is simple then. We aren't going to create a separate file for it, but put it directly below the abstract class definition instead.
network_info.dart
import 'package:data_connection_checker/data_connection_checker.dart';
abstract class NetworkInfo {
Future<bool> get isConnected;
}
class NetworkInfoImpl implements NetworkInfo {
final DataConnectionChecker connectionChecker;
NetworkInfoImpl(this.connectionChecker);
@override
Future<bool> get isConnected => connectionChecker.hasConnection;
}
What's next
While this part may not have been the longest, there's a lot to take in. We implemented the NetworkInfo class with an interesting testing approach and you learned why it's beneficial to create even seemingly "useless" classes just to hide 3rd party code under a stable interface.
There are still Data Sources to implement. In the next part, we're going to work on the local Data Source which means doing TDD with the shared_preferences package. Subscribe below to grow your Flutter coding skills by getting important Flutter news sent right into your inbox on a weekly basis.
[…] aclarar que el contenido original es de Resocoder, lo que he hecho es una traducción al español del contenido. Al final de este artículo está […]
thank you sensei
I have a problem with forwarding the call
I use internet_connection_checker package it’s same as dataConnectionCh… but the null-safety version.
when i forward the call typically like what you did the test did not passed
Expected: <Instance of 'Future’>
Actual: <Instance of 'Future’>
networt_info_test.dart——————————————-
@GenerateMocks([InternetConnectionChecker])
void main() {
late NetworkInfoImpl networkInfoImpl;
late MockInternetConnectionChecker mockInternetConnectionChecker;
setUp(() {
mockInternetConnectionChecker = MockInternetConnectionChecker();
networkInfoImpl = NetworkInfoImpl(mockInternetConnectionChecker);
});
group(‘is connected’, () {
test(‘should forward the call to InternetConnectionChecker.hasConnection’,
() async {
// arrange
final tHasConnectionFuture = Future.value(true);
when(mockInternetConnectionChecker.hasConnection)
.thenAnswer((_) async => tHasConnectionFuture);
// act
final result = networkInfoImpl.isConnected;
// assert
verify(mockInternetConnectionChecker.hasConnection);
expect(result, tHasConnectionFuture);
});
});
}
NetworkInfoImpl
class NetworkInfoImpl implements NetworkInfo {
final InternetConnectionChecker internetConnectionChecker;
NetworkInfoImpl(this.internetConnectionChecker);
@override
Future get isConnected => internetConnectionChecker.hasConnection;
}
import ‘package:internet_connection_checker/internet_connection_checker.dart’;
import ‘package:flutter_test/flutter_test.dart’;
import ‘package:mockito/mockito.dart’;
import ‘package:mockito/annotations.dart’;
import ‘package:study_flutter_clean_architeture/core/network/network_info_impl.dart’;
import ‘network_info_test.mocks.dart’;
@GenerateMocks([InternetConnectionChecker])
void main() {
late final MockInternetConnectionChecker internetConnectionChecker;
late final NetworkInfoImpl networkInfo;
setUpAll(() {
internetConnectionChecker = MockInternetConnectionChecker();
networkInfo = NetworkInfoImpl(
internetConnectionChecker: internetConnectionChecker,
);
when(internetConnectionChecker.hasConnection).thenAnswer((_) async => true);
});
group(‘isConnected’, () {
test(‘should forward the call to DataConnectionChecker.hasConnection’,
() async {
when(internetConnectionChecker.hasConnection)
.thenAnswer((_) async => true);
final result = await networkInfo.isConnected;
verify(internetConnectionChecker.hasConnection);
expect(result, true);
});
});
}
Your article helped me a lot, is there any more related content? Thanks!