Flutter TDD Clean Architecture Course [7] – Network Info

4  comments

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.

TDD Clean Architecture Course
This post is just one part of a tutorial series. See all of the other parts here and learn to architect your Flutter apps!

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:

Note that on Android, this does not guarantee connection to Internet. For instance, the app might have WiFi access but it might be a VPN or a hotel WiFi with no Internet access.

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!

The addresses used point to DNS servers of CloudFlare, Google and OpenDNS. Let's just say that these three services combined have a 100% uptime, so no worries regarding if the online status of the device will be determined truthfully.

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.

Test "mirrored" location

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.

It may seem that such "call forwarding" is just a waste of effort. Quite the opposite!

Imagine you wanted to swap the data_connection_checker package for something different. If you used it directly inside the Repositories (unless you're building a Number Trivia App, you'll have multiple ones), you'd need to change a LOT of connectivity-checking code.

By hiding it behind an interface you control, there won't much code to change at all!

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.

Icons and other attribution GOES HERE

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • 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);
      });
      });
      }

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >