Bloc: Bloc unit test with StreamSubscription on the constructor cause "MissingPluginException"

Created on 24 Feb 2020  ยท  2Comments  ยท  Source: felangel/bloc

Regards, dear @felangel
I have a doubt, when trying to implement the test of a particular Bloc that only allows me to know if the user's device has connectivity or not:

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:connectivity/connectivity.dart';

enum ConnectivityEvent {
  disable,
  enable,
}

class ConnectivityBloc extends Bloc<ConnectivityEvent, bool> {
  StreamSubscription _connectivitySubscription;

  ConnectivityBloc() {
    _connectivitySubscription = Connectivity().onConnectivityChanged.listen(
      (ConnectivityResult result) {
        if (result == ConnectivityResult.none) {
          add(ConnectivityEvent.disable);
        } else {
          add(ConnectivityEvent.enable);
        }
      },
    );
  }

  @override
  bool get initialState => false;

  @override
  Stream<bool> mapEventToState(ConnectivityEvent event) async* {
    switch (event) {
      case ConnectivityEvent.disable:
        yield false;
        break;
      case ConnectivityEvent.enable:
        yield true;
        break;
    }
  }

  @override
  Future<void> close() {
    _connectivitySubscription.cancel();
    return super.close();
  }
}

Using the following unit test:

import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/blocs/blocs.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('ConnectivityBloc', () {
    ConnectivityBloc connectivityBloc;

    setUp(() {
      connectivityBloc = ConnectivityBloc();
    });

    blocTest(
      'initial state is false',
      build: () => connectivityBloc,
      expect: [false],
    );

    blocTest(
      'state should be true on connection enable',
      build: () => connectivityBloc,
      act: (bloc) => bloc.add(ConnectivityEvent.enable),
      expect: [false, true],
    );
  });
}

The console prints out the next message everytime a blocTest is make it:

โ•โ•โ•ก EXCEPTION CAUGHT BY SERVICES LIBRARY โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
The following MissingPluginException was thrown while activating platform stream on channel
plugins.flutter.io/connectivity_status:
MissingPluginException(No implementation found for method listen on channel
plugins.flutter.io/connectivity_status)

When the exception was thrown, this was the stack:
#0      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:319:7)
<asynchronous suspension>
#1      EventChannel.receiveBroadcastStream.<anonymous closure> (package:flutter/src/services/platform_channel.dart:517:29)
#3      EventChannel.receiveBroadcastStream.<anonymous closure> (package:flutter/src/services/platform_channel.dart:503:64)
#14     new ConnectivityBloc (package:uba_app/blocs/connectivity/connectivity_bloc.dart:21:70)
#15     main.<anonymous closure>.<anonymous closure> (file:///C:/Apps/uba_app/test/blocs/connectivity_bloc_test.dart:11:26)
#16     Declarer._runSetUps.<anonymous closure> (package:test_api/src/backend/declarer.dart:294:51)
#28     Declarer._runSetUps (package:test_api/src/backend/declarer.dart:294:18)
#41     Declarer._runSetUps (package:test_api/src/backend/declarer.dart)
(elided 52 frames from package dart:async, package dart:async-patch, and package stack_trace)
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
00:21 +2: All tests passed!

I think I understand the reason for this exception, after all the connectivity package is not available for native Dart and my OS (Windows) can't register it; only for Flutter [Android | iOS].

Is it possible to do the Bloc unit test without having to receive this exception messages?

My project info:

  • flutter: 1.12.13+hotfix.7 stable
  • bloc: 3.0.0
  • flutter_bloc: 3.2.0
  • connectivity: 0.4.8+1
  • flutter_test: sdk: flutter

    • bloc_test: 3.1.0

question

Most helpful comment

Thanks @ResoDev!
When applying your advice of trying to use mockito the exception stopped appearing, as a record, I leave here what I applied:

connectivity_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:connectivity/connectivity.dart';
import 'package:meta/meta.dart';

enum ConnectivityEvent {
  disable,
  enable,
}

class ConnectivityBloc extends Bloc<ConnectivityEvent, bool> {
  final Stream<ConnectivityResult> connectivityStream;
  StreamSubscription _connectivitySubscription;

  ConnectivityBloc({
    @required this.connectivityStream,
  }) {
    _connectivitySubscription = connectivityStream.listen(
      (ConnectivityResult result) {
        if (result == ConnectivityResult.none) {
          add(ConnectivityEvent.disable);
        } else {
          add(ConnectivityEvent.enable);
        }
      },
    );
  }

  @override
  bool get initialState => false;

  @override
  Stream<bool> mapEventToState(ConnectivityEvent event) async* {
    switch (event) {
      case ConnectivityEvent.disable:
        yield false;
        break;
      case ConnectivityEvent.enable:
        yield true;
        break;
    }
  }

  @override
  Future<void> close() {
    _connectivitySubscription.cancel();
    return super.close();
  }
}

connectivity_bloc_test.dart

import 'package:bloc_test/bloc_test.dart';
import 'package:connectivity/connectivity.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:my_app/blocs/blocs.dart';

class MockConnectivity extends Mock implements Connectivity {}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('ConnectivityBloc', () {
    Connectivity connectivity;
    Stream<ConnectivityResult> connectivityStream;
    ConnectivityBloc connectivityBloc;

    setUp(() {
      connectivity = MockConnectivity();
      when(connectivity.onConnectivityChanged).thenAnswer((_) {
        return Stream.value(ConnectivityResult.none);
      });

      connectivityStream = connectivity.onConnectivityChanged;
      connectivityBloc = ConnectivityBloc(connectivityStream: connectivityStream);
    });

    blocTest(
      'initial state is false',
      build: () => connectivityBloc,
      expect: [false],
    );

    blocTest(
      'state should be true on connection enable',
      build: () => connectivityBloc,
      act: (bloc) => bloc.add(ConnectivityEvent.enable),
      expect: [false, true],
    );
  });
}

Console output

$ flutter test
00:06 +2: All tests passed!

All 2 comments

Hello ๐Ÿ‘‹
Connectivity depends on the native platform to access the network state and this is not possible inside a widget test.
Try mocking the instance of Connectivity with mockito.

Thanks @ResoDev!
When applying your advice of trying to use mockito the exception stopped appearing, as a record, I leave here what I applied:

connectivity_bloc.dart

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:connectivity/connectivity.dart';
import 'package:meta/meta.dart';

enum ConnectivityEvent {
  disable,
  enable,
}

class ConnectivityBloc extends Bloc<ConnectivityEvent, bool> {
  final Stream<ConnectivityResult> connectivityStream;
  StreamSubscription _connectivitySubscription;

  ConnectivityBloc({
    @required this.connectivityStream,
  }) {
    _connectivitySubscription = connectivityStream.listen(
      (ConnectivityResult result) {
        if (result == ConnectivityResult.none) {
          add(ConnectivityEvent.disable);
        } else {
          add(ConnectivityEvent.enable);
        }
      },
    );
  }

  @override
  bool get initialState => false;

  @override
  Stream<bool> mapEventToState(ConnectivityEvent event) async* {
    switch (event) {
      case ConnectivityEvent.disable:
        yield false;
        break;
      case ConnectivityEvent.enable:
        yield true;
        break;
    }
  }

  @override
  Future<void> close() {
    _connectivitySubscription.cancel();
    return super.close();
  }
}

connectivity_bloc_test.dart

import 'package:bloc_test/bloc_test.dart';
import 'package:connectivity/connectivity.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:my_app/blocs/blocs.dart';

class MockConnectivity extends Mock implements Connectivity {}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('ConnectivityBloc', () {
    Connectivity connectivity;
    Stream<ConnectivityResult> connectivityStream;
    ConnectivityBloc connectivityBloc;

    setUp(() {
      connectivity = MockConnectivity();
      when(connectivity.onConnectivityChanged).thenAnswer((_) {
        return Stream.value(ConnectivityResult.none);
      });

      connectivityStream = connectivity.onConnectivityChanged;
      connectivityBloc = ConnectivityBloc(connectivityStream: connectivityStream);
    });

    blocTest(
      'initial state is false',
      build: () => connectivityBloc,
      expect: [false],
    );

    blocTest(
      'state should be true on connection enable',
      build: () => connectivityBloc,
      act: (bloc) => bloc.add(ConnectivityEvent.enable),
      expect: [false, true],
    );
  });
}

Console output

$ flutter test
00:06 +2: All tests passed!
Was this page helpful?
0 / 5 - 0 ratings