Testing BLoC in Flutter
Tags: flutter, computerscience, productivity, programming
Published: 2021-02-27 09:50:29

*In case it helped :)*
<a href="https://www.buymeacoffee.com/aseemwangoo" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Pass Me A Coffee!!" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>

<!-- wp:paragraph -->
<p><strong><em>Website: </em></strong><a href="https://flatteredwithflutter.com/testing-bloc-in-flutte/" rel="noreferrer noopener" target="_blank"><strong><em>https://flatteredwithflutter.com/testing-bloc-in-flutte/</em></strong></a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We will cover briefly about</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Convention for Tests</li><li>Mocking Github API</li><li>Writing tests for BLoC</li></ol>
<!-- /wp:list -->

{% youtube s231bqsbe0A %}

<!-- wp:heading {"level":3} -->
<h3>1. Convention for&nbsp;Tests</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>In general,</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>test files should reside inside a <code>test</code> folder located at the root of your Flutter application or package.</li></ul>
<!-- /wp:list -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/1*KUJhCIqtxMGFUxh5ICNgag.png" alt=""/><figcaption>Testing BLoC in&nbsp;Flutter</figcaption></figure>
<!-- /wp:image -->

<!-- wp:list -->
<ul><li>Test files should always end with <code>_test.dart</code>.</li><li>Tests are placed under the main function</li></ul>
<!-- /wp:list -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">void main() {<br>  // ALL YOUR TESTS ARE PLACED HERE<br>}</pre>
<!-- /wp:preformatted -->

<!-- wp:list -->
<ul><li>If you have several tests that are related to one another, combine them using the <code>group</code> function.</li></ul>
<!-- /wp:list -->
<!-- wp:preformatted -->
<pre class="wp-block-preformatted">void main() {
  <code>group('YOUR GROUP OF TESTS', () {
    
    test('TEST 1', () {
      
    });</code>

    <code>test('TEST 2', () {          </code>
    <code>});</code>
  }
}</pre>
<!-- /wp:preformatted -->

<!-- wp:list -->
<ul><li>You can use a terminal to run the tests by</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>flutter test test/'YOUR TEST FILE NAME'.dart</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>2. Mocking Github&nbsp;API</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Introducing <a href="https://pub.dev/packages/mockito" rel="noreferrer noopener" target="_blank"><strong>Mockito</strong></a> (a mock library for Dart).</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>How does it&nbsp;work?</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Let’s say we have a class like this</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>// Real classclass Cat {  String sound() => "Meow";  Future&lt;void> chew() async => print("Chewing...");}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We create a mock class out of our above class by</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>// Mock classclass MockCat extends Mock implements Cat {}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>and create a mock object</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>// Create mock object.var cat = MockCat();</code></pre>
<!-- /wp:code -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4>Integrate our Github API&nbsp;class</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Github has a <a href="https://stackoverflow.com/questions/53747159/whats-the-correct-endpoint-to-access-github-repositories-on-github-api" rel="noreferrer noopener" target="_blank">public endpoint</a> exposed for searching the repositories, and we append the user-defined search term to it.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted"><a href="https://api.github.com/search/repositories?q=%27YOUR" rel="noreferrer noopener" target="_blank">https://api.github.com/search/repositories?q='YOUR</a> SEARCH TERM'</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>We have an implementation class (<strong>GithubApi</strong>) that includes the method for calling the above API.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">class GithubApi implements GithubSearchContract {}</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>and our search function looks like this</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/1*5I2qKaWZ8F7We_yv3h6m0w.png" alt=""/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>where we call the API, fetch the results, and convert them into the <strong>SearchResult</strong> model.</p>
<!-- /wp:paragraph -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4><strong>Time to&nbsp;mock!!</strong></h4>
<!-- /wp:heading -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">class MockGithubSearchImpl extends Mock implements GithubApi {}</pre>
<!-- /wp:preformatted -->

<!-- wp:list -->
<ul><li>As we saw above, we mock our GithubApi, by creating a class <code>MockGithubSearchImpl</code>which extends <code>Mock</code>&nbsp;</li><li>Setup the mock classes inside the test file</li></ul>
<!-- /wp:list -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">SearchBloc searchBloc;
MockGithubSearchImpl mockGithubSearch;

setUp(() {
  mockGithubSearch = MockGithubSearchImpl();
  searchBloc = SearchBloc(mockGithubSearch);
});

tearDown(() {
  searchBloc?.dispose();
});</pre>
<!-- /wp:preformatted -->

<!-- wp:list -->
<ul><li><a href="https://api.flutter.dev/flutter/flutter_test/setUp.html" rel="noreferrer noopener" target="_blank"><strong>setUp</strong></a>: Registers a function to be run before tests.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>We register our bloc with the mock implementation of our API.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><a href="https://api.flutter.dev/flutter/flutter_test/tearDown.html" rel="noreferrer noopener" target="_blank"><strong>tearDown</strong></a>: Registers a function to be run after tests.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>We dispose of our bloc inside the tearDown.</p>
<!-- /wp:paragraph -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4>Define Matchers for&nbsp;States</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We defined 5 UI states as </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>class SearchNoTerm extends SearchState {
  SearchNoTerm() : super(state: States.noTerm);
}
class SearchError extends SearchState {
  SearchError() : super(state: States.error);
}
class SearchLoading extends SearchState {
  SearchLoading() : super(state: States.loading);
}
class SearchPopulated extends SearchState {
  final SearchResult result;
SearchPopulated(this.result) : super(state: States.populated);
}
class SearchEmpty extends SearchState {
  SearchEmpty() : super(state: States.empty);
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>These states will be sent to UI, as per the BLoC logic.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>To ease the testing, we define <a href="https://api.flutter.dev/flutter/package-matcher_matcher/TypeMatcher-class.html" rel="noreferrer noopener" target="_blank"><strong>typematchers</strong></a><strong>,</strong> which basically creates a matcher instance of type [T].</li></ul>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>const noTerm = TypeMatcher&lt;SearchNoTerm>();
const loading = TypeMatcher&lt;SearchLoading>();
const empty = TypeMatcher&lt;SearchEmpty>();
const populated = TypeMatcher&lt;SearchPopulated>();
const error = TypeMatcher&lt;SearchError>();</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>3. Writing tests for BLoC</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We instantiate a new instance <code>SearchBloc</code> inside our <code><a href="https://api.flutter.dev/flutter/flutter_test/setUp.html" rel="noreferrer noopener" target="_blank">setUp</a></code> to ensure that each test is run under the same conditions.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Also, we dispose of the bloc instance inside our <code><a rel="noreferrer noopener" href="https://api.flutter.dev/flutter/flutter_test/tearDown.html" target="_blank">tearDown</a></code> </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>SearchBloc searchBloc;
MockGithubSearchImpl mockGithubSearch;
setUp(() {
  mockGithubSearch = MockGithubSearchImpl();
  searchBloc = SearchBloc(mockGithubSearch);
});
tearDown(() {
  searchBloc?.dispose();
});</code></pre>
<!-- /wp:code -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:list -->
<ul><li><strong>setUp</strong>: Registers a function to be run before tests. This function will be called before each test is run.</li><li><strong>tearDown: </strong>Registers a function to be run after tests. This function will be called after each test is run.</li></ul>
<!-- /wp:list -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4>Let the testing&nbsp;begin</h4>
<!-- /wp:heading -->

<!-- wp:list {"ordered":true} -->
<ol><li><strong>Check for null</strong></li></ol>
<!-- /wp:list -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('throws AssertionError if contract is null', () {
   expect(
     () => SearchBloc(null),
     throwsA(isAssertionError),
   );
})</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We pass in a null value to our <strong>SearchBloc</strong></li><li>The response expected is an <a href="https://api.flutter.dev/flutter/dart-core/AssertionError-class.html" rel="noreferrer noopener" target="_blank"><strong>AssertionError</strong></a></li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>2. <strong>Check for the initial state</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('initial state should be NoTerm', () async {
  await expectLater(searchBloc.state, emitsInOrder(&#91;noTerm]));
});</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>In our <strong>SearchBloc, </strong>the initial state is set to <strong>noTerm (</strong>see above for typeMatcher<strong>)</strong></li><li><strong>expectLater</strong>: Just like <a href="https://api.flutter.dev/flutter/test_api/expect.html" rel="noreferrer noopener" target="_blank">expect</a>, but returns a <a href="https://api.flutter.dev/flutter/dart-async/Future-class.html" rel="noreferrer noopener" target="_blank">Future</a> that completes when the matcher has finished matching.</li><li><strong>emitsInOrder: </strong>Returns a <a href="https://api.flutter.dev/flutter/test_api/StreamMatcher-class.html" rel="noreferrer noopener" target="_blank">StreamMatcher</a> that matches the stream if each matcher in <code>matchers</code> matches, one after another.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>3. <strong>Check for empty term</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('hardcoded empty term', () async {
  expect(searchBloc.state, emitsInOrder(&#91;noTerm, empty]));
  searchBloc.onTextChanged.add('');
})</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We add an empty string into our <strong>onTextChanged sink</strong></li><li>The expected states are <strong>noTerm</strong> and then <strong>empty</strong></li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>4. <strong>Check for results from API</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('api returns results', () async {
final term = 'aseem';
when(searchBloc.api.search(term)).thenAnswer((_) async {
return SearchResult(
     &#91;SearchResultItem('aseem wangoo', 'xyz', 'abc')]);
});
expect(searchBloc.state, emitsInOrder(&#91;noTerm,loading,populated]));
searchBloc.onTextChanged.add(term);
});</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We add a string into our <strong>onTextChanged sink</strong></li><li>Then we fake the response using Mockito stubbing</li><li><a href="https://pub.dev/documentation/mockito/latest/mockito/PostExpectation/thenAnswer.html" rel="noreferrer noopener" target="_blank"><strong>thenAnswer</strong></a>: Store a function which is called and the return value will be returned. (in our case <strong>SearchResult</strong>)</li><li>Expected states are noTerm, loading, and populated.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>5. <strong>Check for no results from API</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('emit empty state if no results', () async {
 final term = 'aseem';
when(searchBloc.api.search(term)).thenAnswer(
  (_) async => SearchResult(&#91;]),
 );
expect(searchBloc.state, emitsInOrder(&#91;noTerm, loading, empty]));
 searchBloc.onTextChanged.add(term);
});</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We add a string into our <strong>onTextChanged sink</strong></li><li>Then we fake the response using Mockito stubbing (an empty <strong>SearchResult</strong>)</li><li>Expected states are noTerm, loading, and empty.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>6. <strong>Check for API down</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('emit error state if API is down', () async {
  final term = 'aseem';
  when(searchBloc.api.search(term)).thenThrow(Exception());
  expect(searchBloc.state, emitsInOrder(&#91;noTerm, loading, error]));
  searchBloc.onTextChanged.add(term);
});</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We add a string into our <strong>onTextChanged sink</strong></li><li>Then we throw an  exception using Mockito stubbing</li><li>Expected states are noTerm, loading, and error.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>7. <strong>Check if the stream is closed</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>test('stream is closed', () async {
   when(searchBloc.dispose());
   expect(searchBloc.state, emitsInOrder(&#91;noTerm, emitsDone]));
});</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>We dispose of the bloc</li><li>Expected states are noTerm and emitsDone.</li><li><strong>emitsDone</strong>: Returns a <a href="https://api.flutter.dev/flutter/test_api/StreamMatcher-class.html" rel="noreferrer noopener" target="_blank">StreamMatcher</a> that asserts that the stream emits a “done” event.</li></ul>
<!-- /wp:list -->


{% youtube s231bqsbe0A %}

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Hosted URL&nbsp;: <a href="https://web.flatteredwithflutter.com/#/" rel="noreferrer noopener" target="_blank">https://web.flatteredwithflutter.com/#/</a></p></blockquote>
<!-- /wp:quote -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><a href="https://github.com/AseemWangoo/experiments_with_web" rel="noreferrer noopener" target="_blank">Source code for Flutter Web App..</a></p></blockquote>
<!-- /wp:quote -->