<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Victor Eronmosele's Blog]]></title><description><![CDATA[A highly motivated software engineer especially experienced as a mobile team lead working with diverse teams from all over the globe. He enjoys building secure,]]></description><link>https://blog.victoreronmosele.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 16:19:10 GMT</lastBuildDate><atom:link href="https://blog.victoreronmosele.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Mocking and Testing Firestore Operations in Flutter Unit Tests | Part 2 (Transactions and Batched Writes)]]></title><description><![CDATA[Introduction
Firestore offers the ability to perform atomic operations — "series of database operations such that either all occurs, or nothing occurs") — in form of Transactions and Batched Writes.

Transactions: a transaction is a set of read and w...]]></description><link>https://blog.victoreronmosele.com/mocking-firestore-flutter-2</link><guid isPermaLink="true">https://blog.victoreronmosele.com/mocking-firestore-flutter-2</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Firebase]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Victor Eronmosele]]></dc:creator><pubDate>Tue, 15 Mar 2022 12:09:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1647346103528/ljjOXimp3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Firestore offers the ability to perform atomic operations — <a target="_blank" href="https://en.wikipedia.org/wiki/Atomicity_(database_systems">"series of database operations such that either all occurs, or nothing occurs"</a>) — in form of Transactions and Batched Writes.</p>
<blockquote>
<p>Transactions: a transaction is a set of read and write operations on one or more documents.</p>
<p>Batched Writes: a batched write is a set of write operations on one or more documents.</p>
<p>Source: <a target="_blank" href="https://firebase.google.com/docs/firestore/manage-data/transactions">Firebase Documentation for Transactions and Batched Writes</a></p>
</blockquote>
<p>This article examines how to write unit tests for Firestore Transactions and Batched Writes in Flutter. </p>
<p>We continue from the last article: <a target="_blank" href="https://blog.victoreronmosele.com/mocking-firestore-flutter">Mocking and Testing Firestore Operations in Flutter Unit Tests | Part 1 (Documents and Collections)</a> where we looked at how to implement unit tests for Firestore operations involving Documents and Collections.</p>
<p>The article was created using <code>Flutter Channel stable, 2.8.1</code>.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>The prerequisites are the same as in the previous article.</p>
<p>Add the following packages to your <code>pubspec.yaml</code> file and run <code>flutter pub get</code>.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">cloud_firestore:</span> <span class="hljs-string">^3.1.4</span>
  <span class="hljs-attr">fake_cloud_firestore:</span> <span class="hljs-string">^1.2.0</span>

<span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">test:</span>
</code></pre>
<p>This adds <code>cloud_firestore</code> which is the Cloud Firestore package, <code>fake_cloud_firestore</code> which is the mock Firestore package and <code>test</code> which is the package used for unit tests.</p>
<h1 id="heading-problem-statement">Problem Statement</h1>
<p>We extend our <code>FirestoreService</code> class from the previous article — which performed read and write operations to Firestore using Documents and Collections — to perform read and write operations to Firestore using Transactions and Batched Writes and we want to write a set of unit tests for this class.</p>
<p>The class is shown below (with the Document and Collection part removed for readability):</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_firestore/cloud_firestore.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FirestoreService</span> </span>{
  <span class="hljs-keyword">const</span> FirestoreService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firestore});

  <span class="hljs-keyword">final</span> FirebaseFirestore firestore;

  <span class="hljs-comment">// Document and Collection operations removed for readability</span>

  Future&lt;<span class="hljs-keyword">void</span>&gt; runTransaction({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToSetTo,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToDelete,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> firestore.runTransaction&lt;<span class="hljs-keyword">void</span>&gt;((transaction) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
          firestore.collection(collectionPath).doc(documentPathToUpdate);
      <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
          firestore.collection(collectionPath).doc(documentPathToSetTo);
      <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
          firestore.collection(collectionPath).doc(documentPathToDelete);

      <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForUpdate =
          <span class="hljs-keyword">await</span> transaction.<span class="hljs-keyword">get</span>(documentReferenceToUpdate);

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataInDocumentPathToUpdate =
          documentSnapshotForUpdate.data() <span class="hljs-keyword">as</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;;

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; updatedData = {
        ...dataInDocumentPathToUpdate,
        ...dataToUpdate
      };

      transaction.update(documentReferenceToUpdate, updatedData);

      transaction.<span class="hljs-keyword">set</span>(documentReferenceToSetTo, dataToSet);

      transaction.delete(documentReferenceToDelete);
    });
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; runBatchedWrite({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToSetTo,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToDelete,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> WriteBatch writeBatch = firestore.batch();

    <span class="hljs-keyword">final</span> CollectionReference collectionReference =
        firestore.collection(collectionPath);

    <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
        collectionReference.doc(documentPathToSetTo);
    <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
        collectionReference.doc(documentPathToUpdate);
    <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
        collectionReference.doc(documentPathToDelete);

    writeBatch.<span class="hljs-keyword">set</span>(documentReferenceToSetTo, dataToSet);

    writeBatch.update(documentReferenceToUpdate, dataToUpdate);

    writeBatch.delete(documentReferenceToDelete);

    <span class="hljs-keyword">await</span> writeBatch.commit();
  }
}
</code></pre>
<h1 id="heading-understanding-the-methods">Understanding The Methods</h1>
<p>This section helps understand the <code>runTransaction</code> and the <code>runBatchedWrite</code> methods with regards to what is expected for unit testing them.</p>
<h3 id="heading-runtransaction">runTransaction</h3>
<pre><code class="lang-dart"> Future&lt;<span class="hljs-keyword">void</span>&gt; runTransaction({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToSetTo,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToDelete,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> firestore.runTransaction&lt;<span class="hljs-keyword">void</span>&gt;((transaction) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
          firestore.collection(collectionPath).doc(documentPathToUpdate);
      <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
          firestore.collection(collectionPath).doc(documentPathToSetTo);
      <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
          firestore.collection(collectionPath).doc(documentPathToDelete);

      <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForUpdate =
          <span class="hljs-keyword">await</span> transaction.<span class="hljs-keyword">get</span>(documentReferenceToUpdate);

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataInDocumentPathToUpdate =
          documentSnapshotForUpdate.data() <span class="hljs-keyword">as</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;;

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; updatedData = {
        ...dataInDocumentPathToUpdate,
        ...dataToUpdate
      };

      transaction.update(documentReferenceToUpdate, updatedData);

      transaction.<span class="hljs-keyword">set</span>(documentReferenceToSetTo, dataToSet);

      transaction.delete(documentReferenceToDelete);
    });
  }
</code></pre>
<p>The <code>runTransaction</code> method demonstrates how to do transactions in Firestore. Since transactions allow you to perform read operations and writes operations, the <code>runTransaction</code> method has both read and write operations in it.</p>
<p>The major operations in this method are below:</p>
<ul>
<li>The transaction reads the data at <code>documentPathToUpdate</code> and merges it with <code>dataToUpdate</code> and updates the document there.</li>
<li>The transaction sets <code>dataToSet</code> to the document at the <code>documentPathToSetTo</code>.</li>
<li>The transaction deletes the document at <code>documentPathToDelete</code>.</li>
</ul>
<h3 id="heading-runbatchedwrite">runBatchedWrite</h3>
<pre><code class="lang-dart">  Future&lt;<span class="hljs-keyword">void</span>&gt; runBatchedWrite({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToSetTo,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToUpdate,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPathToDelete,
  }) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> WriteBatch writeBatch = firestore.batch();

    <span class="hljs-keyword">final</span> CollectionReference collectionReference =
        firestore.collection(collectionPath);

    <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
        collectionReference.doc(documentPathToSetTo);
    <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
        collectionReference.doc(documentPathToUpdate);
    <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
        collectionReference.doc(documentPathToDelete);

    writeBatch.<span class="hljs-keyword">set</span>(documentReferenceToSetTo, dataToSet);

    writeBatch.update(documentReferenceToUpdate, dataToUpdate);

    writeBatch.delete(documentReferenceToDelete);

    <span class="hljs-keyword">await</span> writeBatch.commit();
  }
</code></pre>
<p>The <code>runBatchedWrite</code> method demonstrates how to perform batched writes in Firestore. Since batched writes allow you to perform only write operations, the <code>runBatchedWrite</code> method contains only write operations.</p>
<p>The major operations in this method are below:</p>
<ul>
<li>The batched write updates the document at <code>documentPathToUpdate</code> with <code>dataToUpdate</code>.</li>
<li>The batched write  sets <code>dataToSet</code> to the document at the <code>documentPathToSetTo</code>.</li>
<li>The batched write deletes the document at <code>documentPathToDelete</code>.</li>
</ul>
<h1 id="heading-testing-approach">Testing Approach</h1>
<p>Just as it was outlined in the <a target="_blank" href="https://blog.victoreronmosele.com/mocking-firestore-flutter#heading-testing-approach">previous article</a>, we do the following to test the methods:</p>
<ul>
<li><p><em>Construction Injection</em>: </p>
<p> The <code>FakeFirebaseFirestore</code> object is passed into the <code>FirestoreService</code> instance to mock the <code>FirebaseFirestore</code> object.</p>
</li>
<li><p><em>Mocking Strategy &amp; Database State Set Up</em>: </p>
<p> Data is added to specific document/collection paths in the <code>FakeFirebaseFirestore</code> object before carrying out the operation in the <code>FirestoreService</code> class. </p>
<p> Data is then retrieved from the <code>FakeFirebaseFirestore</code> object at specific paths to compare them with the expected values by asserting that they are the same.</p>
</li>
</ul>
<p>The two methods above, individually, contain different read and/or write operations and those are the operations that will be tested.</p>
<p>The tests will be written in the same test file from the <a target="_blank" href="https://blog.victoreronmosele.com/mocking-firestore-flutter#heading-testing-approach">previous article</a></p>
<h1 id="heading-testing-the-transactions-and-batched-writes-class">Testing the Transactions and Batched Writes class</h1>
<h3 id="heading-test-for-running-a-transaction">Test For Running A Transaction</h3>
<h4 id="heading-runtransactions-test"><em>runTransaction's Test</em></h4>
<p>The runTransaction method can be tested by:</p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>, to the service.</li>
<li>Prepopulating the document at <code>documentPathToUpdate</code> via the fakeFirebaseFirestore object with the <code>data</code> object.</li>
<li>Prepopulating the document at <code>documentPathToDelete</code> via the fakeFirebaseFirestore object with the <code>data</code> object.</li>
<li>Running the <code>runTransaction</code> method and passing <code>dataToUpdateWith</code> and <code>dataToSet</code> which will be used by the method to write to <code>documentPathToUpdate</code> and <code>documentPathToSetTo</code>.</li>
<li>Retrieving the actual data contained in the <code>documentPathToUpdate</code>, <code>documentPathToSetTo</code> and <code>documentPathToDelete</code> document paths.</li>
<li>Asserting that the data in <code>documentPathToUpdate</code> is equal to the expected updated data, <code>expectedUpdatedData</code> i.e <code>dataToUpdateWith</code> merged with <code>data</code>.</li>
<li>Asserting that the data in <code>documentPathToSetTo</code> is equal to the expected set data i.e <code>dataToSet</code>.</li>
<li>Asserting that the data in <code>documentPathToDelete</code> is null.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart"> test(<span class="hljs-string">'runTransaction runs the correct transaction operation'</span>, () <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> FirestoreService firestoreService =
      FirestoreService(firestore: fakeFirebaseFirestore!);

  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToUpdate = <span class="hljs-string">'documentPathToUpdate'</span>;
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToSetTo = <span class="hljs-string">'documentPathToSetTo'</span>;
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToDelete = <span class="hljs-string">'documentPathToDelete'</span>;

  <span class="hljs-keyword">final</span> CollectionReference collectionReference =
      fakeFirebaseFirestore!.collection(collectionPath);

  <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
      collectionReference.doc(documentPathToUpdate);
  <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
      collectionReference.doc(documentPathToSetTo);
  <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
      collectionReference.doc(documentPathToDelete);

  documentReferenceToUpdate.<span class="hljs-keyword">set</span>(data);
  documentReferenceToDelete.<span class="hljs-keyword">set</span>(data);

  <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdateWith = {<span class="hljs-string">'updated_data'</span>: <span class="hljs-string">'43'</span>};
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet = {<span class="hljs-string">'data'</span>: <span class="hljs-number">44</span>};

  <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedUpdatedData = {
    ...data,
    ...dataToUpdateWith
  };

  <span class="hljs-keyword">await</span> firestoreService.runTransaction(
      dataToUpdate: dataToUpdateWith,
      dataToSet: dataToSet,
      collectionPath: collectionPath,
      documentPathToUpdate: documentPathToUpdate,
      documentPathToSetTo: documentPathToSetTo,
      documentPathToDelete: documentPathToDelete);

  <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForUpdate =
      <span class="hljs-keyword">await</span> documentReferenceToUpdate.<span class="hljs-keyword">get</span>();
  <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForSet =
      <span class="hljs-keyword">await</span> documentReferenceToSetTo.<span class="hljs-keyword">get</span>();
  <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForDelete =
      <span class="hljs-keyword">await</span> documentReferenceToDelete.<span class="hljs-keyword">get</span>();

  <span class="hljs-keyword">final</span> actualDataFromUpdate = documentSnapshotForUpdate.data();
  <span class="hljs-keyword">final</span> actualDataFromSet = documentSnapshotForSet.data();
  <span class="hljs-keyword">final</span> actualDataFromDelete = documentSnapshotForDelete.data();

  expect(actualDataFromUpdate, expectedUpdatedData);
  expect(actualDataFromSet, dataToSet);
  expect(actualDataFromDelete, <span class="hljs-keyword">null</span>);
});
</code></pre>
<h3 id="heading-test-for-running-a-batched-write">Test For Running A Batched Write</h3>
<h4 id="heading-runbatchedwrites-test"><em>runBatchedWrite's Test</em></h4>
<p>The runBatchedWrite method can be tested by:</p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>, to the service.</li>
<li>Prepopulating the document at <code>documentPathToUpdate</code> via the fakeFirebaseFirestore object with the <code>data</code> object.</li>
<li>Prepopulating the document at <code>documentPathToDelete</code> via the fakeFirebaseFirestore object with the <code>data</code> object.</li>
<li>Running the <code>runBatchedWrite</code> method and passing <code>dataToUpdateWith</code> and <code>dataToSet</code> which will be used by the method to write to <code>documentPathToUpdate</code> and <code>documentPathToSetTo</code>.</li>
<li>Retrieving the actual data contained in the <code>documentPathToUpdate</code>, <code>documentPathToSetTo</code> and <code>documentPathToDelete</code> document paths.</li>
<li>Asserting that the data in <code>documentPathToUpdate</code> is equal to the expected updated data, <code>expectedUpdatedData</code> i.e <code>dataToUpdateWith</code> merged with <code>data</code>.</li>
<li>Asserting that the data in <code>documentPathToSetTo</code> is equal to the expected set data i.e <code>dataToSet</code>.</li>
<li>Asserting that the data in <code>documentPathToDelete</code> is null.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart"> test(<span class="hljs-string">'runBatchedWrite runs the correct batched write operation'</span>,
    () <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> FirestoreService firestoreService =
      FirestoreService(firestore: fakeFirebaseFirestore!);
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToUpdate = <span class="hljs-string">'documentPathToUpdate'</span>;
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToSetTo = <span class="hljs-string">'documentPathToSetTo'</span>;
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToDelete = <span class="hljs-string">'documentPathToDelete'</span>;

  <span class="hljs-keyword">final</span> CollectionReference collectionReference =
      fakeFirebaseFirestore!.collection(collectionPath);

  <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
      collectionReference.doc(documentPathToUpdate);
  <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
      collectionReference.doc(documentPathToSetTo);
  <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
      collectionReference.doc(documentPathToDelete);

  documentReferenceToUpdate.<span class="hljs-keyword">set</span>(data);
  documentReferenceToDelete.<span class="hljs-keyword">set</span>(data);

  <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdateWith = {<span class="hljs-string">'updated_data'</span>: <span class="hljs-string">'43'</span>};
  <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet = {<span class="hljs-string">'data'</span>: <span class="hljs-number">44</span>};

  <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedUpdatedData = {
    ...data,
    ...dataToUpdateWith
  };

  <span class="hljs-keyword">await</span> firestoreService.runBatchedWrite(
      dataToUpdate: dataToUpdateWith,
      dataToSet: dataToSet,
      collectionPath: collectionPath,
      documentPathToUpdate: documentPathToUpdate,
      documentPathToSetTo: documentPathToSetTo,
      documentPathToDelete: documentPathToDelete);

  <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForUpdate =
      <span class="hljs-keyword">await</span> documentReferenceToUpdate.<span class="hljs-keyword">get</span>();
  <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForSet =
      <span class="hljs-keyword">await</span> documentReferenceToSetTo.<span class="hljs-keyword">get</span>();
  <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForDelete =
      <span class="hljs-keyword">await</span> documentReferenceToDelete.<span class="hljs-keyword">get</span>();

  <span class="hljs-keyword">final</span> actualDataFromUpdate = documentSnapshotForUpdate.data();
  <span class="hljs-keyword">final</span> actualDataFromSet = documentSnapshotForSet.data();
  <span class="hljs-keyword">final</span> actualDataFromDelete = documentSnapshotForDelete.data();

  expect(actualDataFromUpdate, expectedUpdatedData);
  expect(actualDataFromSet, dataToSet);
  expect(actualDataFromDelete, <span class="hljs-keyword">null</span>);
});
</code></pre>
<h1 id="heading-complete-test-file">Complete Test File</h1>
<p>The content of the entire test file is shown below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_firestore/cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:fake_cloud_firestore/fake_cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firestore_unit_test_flutter/firestore_service.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'FirestoreService'</span>, () {
    FakeFirebaseFirestore? fakeFirebaseFirestore;
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data = {<span class="hljs-string">'data'</span>: <span class="hljs-string">'42'</span>};

    setUp(() {
      fakeFirebaseFirestore = FakeFirebaseFirestore();
    });

    <span class="hljs-comment">// Document operations and Collection operations tests removed for readability</span>

    group(<span class="hljs-string">'Transaction Operation'</span>, () {
      test(<span class="hljs-string">'runTransaction runs the correct transaction operation'</span>, () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToUpdate = <span class="hljs-string">'documentPathToUpdate'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToSetTo = <span class="hljs-string">'documentPathToSetTo'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToDelete = <span class="hljs-string">'documentPathToDelete'</span>;

        <span class="hljs-keyword">final</span> CollectionReference collectionReference =
            fakeFirebaseFirestore!.collection(collectionPath);

        <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
            collectionReference.doc(documentPathToUpdate);
        <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
            collectionReference.doc(documentPathToSetTo);
        <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
            collectionReference.doc(documentPathToDelete);

        documentReferenceToUpdate.<span class="hljs-keyword">set</span>(data);
        documentReferenceToDelete.<span class="hljs-keyword">set</span>(data);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdateWith = {<span class="hljs-string">'updated_data'</span>: <span class="hljs-string">'43'</span>};
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet = {<span class="hljs-string">'data'</span>: <span class="hljs-number">44</span>};

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedUpdatedData = {
          ...data,
          ...dataToUpdateWith
        };

        <span class="hljs-keyword">await</span> firestoreService.runTransaction(
            dataToUpdate: dataToUpdateWith,
            dataToSet: dataToSet,
            collectionPath: collectionPath,
            documentPathToUpdate: documentPathToUpdate,
            documentPathToSetTo: documentPathToSetTo,
            documentPathToDelete: documentPathToDelete);

        <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForUpdate =
            <span class="hljs-keyword">await</span> documentReferenceToUpdate.<span class="hljs-keyword">get</span>();
        <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForSet =
            <span class="hljs-keyword">await</span> documentReferenceToSetTo.<span class="hljs-keyword">get</span>();
        <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForDelete =
            <span class="hljs-keyword">await</span> documentReferenceToDelete.<span class="hljs-keyword">get</span>();

        <span class="hljs-keyword">final</span> actualDataFromUpdate = documentSnapshotForUpdate.data();
        <span class="hljs-keyword">final</span> actualDataFromSet = documentSnapshotForSet.data();
        <span class="hljs-keyword">final</span> actualDataFromDelete = documentSnapshotForDelete.data();

        expect(actualDataFromUpdate, expectedUpdatedData);
        expect(actualDataFromSet, dataToSet);
        expect(actualDataFromDelete, <span class="hljs-keyword">null</span>);
      });
    });

    group(<span class="hljs-string">'Batched Write Operation'</span>, () {
      test(<span class="hljs-string">'runBatchedWrite runs the correct batched write operation'</span>,
          () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToUpdate = <span class="hljs-string">'documentPathToUpdate'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToSetTo = <span class="hljs-string">'documentPathToSetTo'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPathToDelete = <span class="hljs-string">'documentPathToDelete'</span>;

        <span class="hljs-keyword">final</span> CollectionReference collectionReference =
            fakeFirebaseFirestore!.collection(collectionPath);

        <span class="hljs-keyword">final</span> DocumentReference documentReferenceToUpdate =
            collectionReference.doc(documentPathToUpdate);
        <span class="hljs-keyword">final</span> DocumentReference documentReferenceToSetTo =
            collectionReference.doc(documentPathToSetTo);
        <span class="hljs-keyword">final</span> DocumentReference documentReferenceToDelete =
            collectionReference.doc(documentPathToDelete);

        documentReferenceToUpdate.<span class="hljs-keyword">set</span>(data);
        documentReferenceToDelete.<span class="hljs-keyword">set</span>(data);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToUpdateWith = {<span class="hljs-string">'updated_data'</span>: <span class="hljs-string">'43'</span>};
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataToSet = {<span class="hljs-string">'data'</span>: <span class="hljs-number">44</span>};

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedUpdatedData = {
          ...data,
          ...dataToUpdateWith
        };

        <span class="hljs-keyword">await</span> firestoreService.runBatchedWrite(
            dataToUpdate: dataToUpdateWith,
            dataToSet: dataToSet,
            collectionPath: collectionPath,
            documentPathToUpdate: documentPathToUpdate,
            documentPathToSetTo: documentPathToSetTo,
            documentPathToDelete: documentPathToDelete);

        <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForUpdate =
            <span class="hljs-keyword">await</span> documentReferenceToUpdate.<span class="hljs-keyword">get</span>();
        <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForSet =
            <span class="hljs-keyword">await</span> documentReferenceToSetTo.<span class="hljs-keyword">get</span>();
        <span class="hljs-keyword">final</span> DocumentSnapshot documentSnapshotForDelete =
            <span class="hljs-keyword">await</span> documentReferenceToDelete.<span class="hljs-keyword">get</span>();

        <span class="hljs-keyword">final</span> actualDataFromUpdate = documentSnapshotForUpdate.data();
        <span class="hljs-keyword">final</span> actualDataFromSet = documentSnapshotForSet.data();
        <span class="hljs-keyword">final</span> actualDataFromDelete = documentSnapshotForDelete.data();

        expect(actualDataFromUpdate, expectedUpdatedData);
        expect(actualDataFromSet, dataToSet);
        expect(actualDataFromDelete, <span class="hljs-keyword">null</span>);
      });
    });
  });
}

<span class="hljs-comment">// MapListContains Matcher code removed for readability</span>
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This article demonstrates how to write unit tests for Firebase Firestore's atomic operations, that is, Transactions and Batched Writes. </p>
<p>The complete code can be found on <a target="_blank" href="https://github.com/victoreronmosele/firestore-unit-test-flutter">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Mocking and Testing Firestore Operations in Flutter Unit Tests | Part 1 (Documents and Collections)]]></title><description><![CDATA[Introduction
This article examines  Firestore operations with respect to Documents and Collections and demonstrates how to mock and test them.
The article was created using Flutter Channel stable, 2.8.1.
Prerequisites
Add the following packages to yo...]]></description><link>https://blog.victoreronmosele.com/mocking-firestore-flutter</link><guid isPermaLink="true">https://blog.victoreronmosele.com/mocking-firestore-flutter</guid><category><![CDATA[Firebase]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Testing]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Victor Eronmosele]]></dc:creator><pubDate>Tue, 15 Feb 2022 00:57:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644886416938/-SE6rnxW0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>This article examines  <a target="_blank" href="https://firebase.google.com/docs/firestore">Firestore</a> operations with respect to Documents and Collections and demonstrates how to mock and test them.</p>
<p>The article was created using <code>Flutter Channel stable, 2.8.1</code>.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>Add the following packages to your <code>pubspec.yaml</code> file and run <code>flutter pub get</code>.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">cloud_firestore:</span> <span class="hljs-string">^3.1.4</span>

<span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">test:</span>
  <span class="hljs-attr">fake_cloud_firestore:</span> <span class="hljs-string">^1.2.0</span>
</code></pre>
<p>This adds <code>cloud_firestore</code> which is the Cloud Firestore package, <code>test</code> which is the package used for unit tests and <code>fake_cloud_firestore</code> which is the mock Firestore package.</p>
<h1 id="heading-problem-statement">Problem Statement</h1>
<p>We have a class <code>FirestoreService</code> which performs read and write operations to Firestore and we want to write a set of unit tests for this class.</p>
<p>The class is shown below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_firestore/cloud_firestore.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FirestoreService</span> </span>{
  <span class="hljs-keyword">const</span> FirestoreService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firestore});

  <span class="hljs-keyword">final</span> FirebaseFirestore firestore;

  <span class="hljs-comment">/// <span class="markdown">Collection Operations</span></span>

  Future&lt;DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; addToCollection(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data,
      <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath}) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).add(data);
  }

  Future&lt;QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getFromCollection(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath}) {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).<span class="hljs-keyword">get</span>();
  }

  Stream&lt;QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getSnapshotStreamFromCollection(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath}) {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).snapshots();
  }

  <span class="hljs-comment">/// <span class="markdown">Document Operations</span></span>

  Future&lt;<span class="hljs-keyword">void</span>&gt; deleteDocumentFromCollection(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath, <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).delete();
  }

  Future&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getFromDocument(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath, <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).<span class="hljs-keyword">get</span>();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; setDataOnDocument(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data,
      <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
      <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).<span class="hljs-keyword">set</span>(data);
  }

  Stream&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getSnapshotStreamFromDocument(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath, <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).snapshots();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; updateDataOnDocument(
      {<span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data,
      <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
      <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
    <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).update(data);
  }
}
</code></pre>
<h1 id="heading-testing-approach">Testing Approach</h1>
<h3 id="heading-construction-injection">Construction Injection</h3>
<p>Since the class <code>FirestoreService</code> injects the <code>Firestore</code> object through its constructor, we can pass the <code>FakeFirebaseFirestore</code> object from the <code>fake_cloud_firestore</code> package and use that to test the methods in the class.</p>
<h3 id="heading-mocking-strategy">Mocking Strategy</h3>
<p>The <code>FakeFirebaseFirestore</code> object works like the <code>Firestore</code> object and the read and write operations that can be used to retrieve and add data to the <code>Firestore</code> object can also be used to retrieve and add data to the <code>FakeFirebaseFirestore</code> object. </p>
<p>This helps us prepopulate the mock object right before we call our methods and helps confirm the expected state of the <code>Firestore</code> database.</p>
<h3 id="heading-database-state-set-up">Database State Set Up</h3>
<ul>
<li><h4 id="heading-initial-state"><em>Initial State</em></h4>
<p>The initial state of the <code>FakeFirebaseFirestore</code> object can be set up by calling the regular write operations on the <code>CollectionReference</code> and <code>DocumentReference</code>.</p>
<p>For instance, we want the initial state of the database to have an object <code>{answer: 42}</code> written to a document <code>lifeTheUniverseAndEverything</code> in collection <code>answers</code>, we write the code below to set it up:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> FakeFirebaseFirestore fakeFirebaseFirestore = FakeFirebaseFirestore();

<span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'answers'</span>;
<span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'lifeTheUniverseAndEverything'</span>;

<span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data = {<span class="hljs-string">'answer'</span>: <span class="hljs-number">42</span>};

<span class="hljs-keyword">await</span> fakeFirebaseFirestore
    .collection(collectionPath)
    .doc(documentPath)
    .<span class="hljs-keyword">set</span>(data);
</code></pre>
</li>
<li><h4 id="heading-final-state"><em>Final State</em></h4>
<p>The final state of the <code>FakeFirebaseFirestore</code> object can be gotten by calling the regular read operations on the <code>CollectionReference</code> and <code>DocumentReference</code>.</p>
<p>Now, to get the final state of the <code>FakeFirebaseFirestore</code> object from above, we write the code below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentSnapshot =
    <span class="hljs-keyword">await</span> fakeFirebaseFirestore
        .collection(collectionPath)
        .doc(documentPath)
        .<span class="hljs-keyword">get</span>();
<span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; actualData = documentSnapshot.data()!;

<span class="hljs-built_in">print</span>(actualData); <span class="hljs-comment">// {'answer': 42}</span>
</code></pre>
</li>
<li><h4 id="heading-assertion"><em>Assertion</em></h4>
<p>Once we have our data, our database's initial state set up and the final state retrieved, we can assert that the data in the database matches the expected data.</p>
<p>Going by what we have above, we can write the assertion using the test package's <code>expect</code> method like below:</p>
<pre><code class="lang-dart">expect(actualData, data);  <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
</code></pre>
</li>
</ul>
<h1 id="heading-testing-the-firestoreservice-class">Testing The FirestoreService Class</h1>
<p>We will test the <code>FirestoreService</code> class by following the approach described in the <code>Testing Approach</code> section above.</p>
<p>This section will be divided into three:</p>
<ul>
<li>Set-up (where the objects needed for the tests are created)</li>
<li>Collection Operations Testing (where operations involving Collections are tested)</li>
<li>Document Operations Testing (where operations involving Documents are tested)</li>
</ul>
<h3 id="heading-set-up">Set-up</h3>
<p>Create a test file <code>firestore_service_test.dart</code> and add the following code to it:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:fake_cloud_firestore/fake_cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'FirestoreService'</span>, () {
    FakeFirebaseFirestore? fakeFirebaseFirestore;
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data = {<span class="hljs-string">'data'</span>: <span class="hljs-string">'42'</span>};

    setUp(() {
      fakeFirebaseFirestore = FakeFirebaseFirestore();
    });
  });
}
</code></pre>
<p>This creates the <code>FakeFirebaseFirestore</code> object and the data needed for every test we'll write for the <code>FirestoreService</code> class. All of the tests are put inside the "FirestoreService" group.</p>
<h3 id="heading-collection-operations-testing">Collection Operations Testing</h3>
<ul>
<li><h4 id="heading-map-equality"><em>Map Equality</em></h4>
<p>This section will be testing Collection operations and this means we will be asserting that the collection contains the data.
Typically, that is done by using the <a target="_blank" href="https://api.flutter.dev/flutter/package-matcher_matcher/contains.html">contains</a> matcher. </p>
<blockquote>
<p>Returns a matcher that matches if the match argument contains the expected value.</p>
</blockquote>
<p>This is how an assertion using the <code>contains</code> matcher would be written:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">int</span>&gt; intList = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>];
<span class="hljs-keyword">final</span> <span class="hljs-built_in">int</span> intData = <span class="hljs-number">2</span>;

expect(intList, contains(intData));  <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
</code></pre>
<p>This passes but when it comes to Lists of Maps, it's a different case. </p>
<p>The assertion below fails:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; mapList = [
    {<span class="hljs-string">'data'</span>: <span class="hljs-number">42</span>}
];
<span class="hljs-keyword">final</span> mapData = {<span class="hljs-string">'data'</span>: <span class="hljs-number">42</span>};
expect(mapList, contains(mapData)); <span class="hljs-comment">/// <span class="markdown">Fails ❌</span></span>
</code></pre>
<p>This is because the <code>contains</code>  matcher for Lists is implemented this way:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">return</span> item.contains(_expected);
</code></pre>
<p>which in turn uses the <code>contains</code> method on List which is implemented like below:</p>
<pre><code class="lang-dart"><span class="hljs-built_in">bool</span> contains(<span class="hljs-built_in">Object?</span> element) {
 <span class="hljs-keyword">for</span> (E e <span class="hljs-keyword">in</span> <span class="hljs-keyword">this</span>) {
   <span class="hljs-keyword">if</span> (e == element) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
 }
 <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}
</code></pre>
<p>The code above checks that the <code>e</code> object is the same object as the <code>element</code> object. And the <code>mapData</code> object won't be the same as the one contained in the List. That explains why the test fails.</p>
</li>
</ul>
<ul>
<li><h4 id="heading-mapequals"><em>mapEquals</em></h4>
<p>  In order to get around this, we can make use of the <a target="_blank" href="https://api.flutter.dev/flutter/foundation/mapEquals.html">mapEquals</a> function that does the following:</p>
<blockquote>
<p>Compares two maps for element-by-element equality.</p>
<p>Returns true if the maps are both null, or if they are both non-null, they have the same lengths and they contain the same keys associated with the same values. Returns false otherwise.</p>
</blockquote>
<p> We can check if any of the element in the list is equal to the <code>mapData</code> object by using the <code>mapEquals</code> function like this:</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; mapList = [
         {<span class="hljs-string">'data'</span>: <span class="hljs-number">42</span>}
 ];
 <span class="hljs-keyword">final</span> mapData = {<span class="hljs-string">'data'</span>: <span class="hljs-number">42</span>};
 expect(mapList.any((element) =&gt; mapEquals(element, mapData)), <span class="hljs-keyword">true</span>); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
</code></pre>
<p> This test passes because the equality check using <code>mapEquals</code> compares the two Map objects by confirming that the keys and values are the same.</p>
</li>
<li><h4 id="heading-custom-matcher"><em>Custom Matcher</em></h4>
<p> While using the check above would work for our tests, it's cleaner and more readable to follow the Flutter convention of using a Matcher to assert our test.</p>
<p> We move the logic <code>mapList.any((element) =&gt; mapEquals(element, mapData))</code> into the Matcher like this below:</p>
<pre><code class="lang-dart"> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MapListContains</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Matcher</span> </span>{
   <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">dynamic</span>, <span class="hljs-built_in">dynamic</span>&gt; _expected;

   <span class="hljs-keyword">const</span> MapListContains(<span class="hljs-keyword">this</span>._expected);

   <span class="hljs-meta">@override</span>
   Description describe(Description description) {
     <span class="hljs-keyword">return</span> description.add(<span class="hljs-string">'contains '</span>).addDescriptionOf(_expected);
   }

   <span class="hljs-meta">@override</span>
   <span class="hljs-built_in">bool</span> matches(<span class="hljs-built_in">dynamic</span> item, <span class="hljs-built_in">Map</span> matchState) {
     <span class="hljs-keyword">if</span> (item <span class="hljs-keyword">is</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&gt;) {
       <span class="hljs-keyword">return</span> item.any((element) =&gt; mapEquals(element, _expected));
     }
     <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
   }
 }
</code></pre>
<p> The <code>MapListContains</code> class extends the <code>Matcher</code> abstract class and overrides the <code>describe</code> method and the <code>matches</code> method.
 The assertion logic is implemented in the <code>matches</code> method in the <code>MapListContains</code> class. </p>
<p> So we can update the test to this below:</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; mapList = [
         {<span class="hljs-string">'data'</span>: <span class="hljs-number">42</span>}
 ];
 <span class="hljs-keyword">final</span> mapData = {<span class="hljs-string">'data'</span>: <span class="hljs-number">42</span>};
 expect(mapList, MapListContains(mapData));  <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
</code></pre>
<p> And this test passes.</p>
</li>
<li><h4 id="heading-set-up-1"><em>Set-up</em></h4>
<p>  Now, we move to the actual test set-up.</p>
<p>  We first create a group "Collection Operations" to hold tests for collection operations.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:fake_cloud_firestore/fake_cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'FirestoreService'</span>, () {
    FakeFirebaseFirestore? fakeFirebaseFirestore;
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data = {<span class="hljs-string">'data'</span>: <span class="hljs-string">'42'</span>};

    setUp(() {
       fakeFirebaseFirestore = FakeFirebaseFirestore();
    });

    group(<span class="hljs-string">'Collection Operations'</span>, () {

    });
  });
 }
</code></pre>
<p> We, can add each test inside of the group.</p>
</li>
<li><h4 id="heading-test-for-adding-to-a-collection"><em>Test For Adding To A Collection.</em></h4>
<pre><code class="lang-dart"> Future&lt;DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; addToCollection(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data,
     <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath}) <span class="hljs-keyword">async</span> {
   <span class="hljs-keyword">return</span> firestore.collection(collectionPath).add(data);
 }
</code></pre>
<p> The <code>addToCollection</code> method above can be tested by:</p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Adding the data to the collection via the service's <code>addToCollection</code> method.</li>
<li>Retrieving the actual data list in the collection via <code>fakeFirebaseFirestore</code>.</li>
<li>Asserting that the actual data list contains the added data using the <code>MapListContains</code> matcher.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart"> test(<span class="hljs-string">'addToCollection adds data to given collection'</span>, () <span class="hljs-keyword">async</span> {
     <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);
     <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

     <span class="hljs-keyword">await</span> firestoreService.addToCollection(
            data: data, collectionPath: collectionPath);

     <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDataList =
            (<span class="hljs-keyword">await</span> fakeFirebaseFirestore!.collection(<span class="hljs-string">'collectionPath'</span>).<span class="hljs-keyword">get</span>())
                .docs
                .map((e) =&gt; e.data())
                .toList();

     expect(actualDataList, <span class="hljs-keyword">const</span> MapListContains(data));  <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
 });
</code></pre>
</li>
<li><h4 id="heading-test-for-getting-data-from-a-collection"><em>Test For Getting Data From A Collection.</em></h4>
<pre><code class="lang-dart">Future&lt;QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getFromCollection() {
  <span class="hljs-keyword">return</span> firestore.collection(<span class="hljs-string">'collectionPath'</span>).<span class="hljs-keyword">get</span>();
}
</code></pre>
<p> The <code>getFromCollection</code> method above can be tested by:</p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Adding the data to the collection via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Retrieving the actual data list in the collection via the service's <code>getFromCollection</code> method.</li>
<li>Asserting that the data list contains the added data using the <code>MapListContains</code> matcher.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart"> test(<span class="hljs-string">'getFromCollection gets data from a given collection'</span>, () <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

    <span class="hljs-keyword">await</span> fakeFirebaseFirestore!.collection(collectionPath).add(data);

    <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; dataList = (<span class="hljs-keyword">await</span> firestoreService
                .getFromCollection(collectionPath: collectionPath))
            .docs
            .map((e) =&gt; e.data())
            .toList();

    expect(dataList, <span class="hljs-keyword">const</span> MapListContains(data)); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
 });
</code></pre>
</li>
<li><h4 id="heading-test-for-getting-a-snapshot-stream-from-a-collection"><em>Test For Getting A Snapshot Stream From A Collection.</em></h4>
<pre><code class="lang-dart">Stream&lt;QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getSnapshotStreamFromCollection(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath}) {
  <span class="hljs-keyword">return</span> firestore.collection(collectionPath).snapshots();
}
</code></pre>
<p> The <code>getSnapshotStreamFromCollection</code> method above can be tested by:</p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service</li>
<li>Adding the data to the collection via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Retrieving the expected snapshot stream from the <code>fakeFirebaseFirestore</code> object and the actual snapshot stream from the service's <code>getSnapshotStreamFromCollection</code> method.</li>
<li>Getting the data from the two snapshot streams and putting them into individual lists (the actual data list and the expected data list).</li>
<li>Asserting that the actual data list is equal to the expected data list.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart"> test( <span class="hljs-string">'getSnapshotStreamFromCollection returns a stream of QuerySnaphot containing the data added'</span>, () <span class="hljs-keyword">async</span> {
     <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);
     <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

     <span class="hljs-keyword">final</span> CollectionReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; collectionReference =
            fakeFirebaseFirestore!.collection(collectionPath);
     <span class="hljs-keyword">await</span> collectionReference.add(data);

     <span class="hljs-keyword">final</span> Stream&lt;QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt;
            expectedSnapshotStream = collectionReference.snapshots();

     <span class="hljs-keyword">final</span> actualSnapshotStream = firestoreService
            .getSnapshotStreamFromCollection(collectionPath: collectionPath);

     <span class="hljs-keyword">final</span> QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedQuerySnapshot =
            <span class="hljs-keyword">await</span> expectedSnapshotStream.first;
     <span class="hljs-keyword">final</span> QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualQuerySnapshot =
            <span class="hljs-keyword">await</span> actualSnapshotStream.first;

     <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedDataList =
            expectedQuerySnapshot.docs.map((e) =&gt; e.data()).toList();
     <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDataList =
            actualQuerySnapshot.docs.map((e) =&gt; e.data()).toList();

     expect(actualDataList, expectedDataList); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
 });
</code></pre>
</li>
</ul>
<h3 id="heading-document-operations-testing">Document Operations Testing</h3>
<ul>
<li><h4 id="heading-test-for-deleting-a-document-from-a-collection"><em>Test For Deleting A Document From A Collection.</em></h4>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; deleteDocumentFromCollection(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath, <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).delete();
}
</code></pre>
<p> The <code>deleteDocumentFromCollection</code> method above can be tested by:</p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Adding the data to the collection via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Deleting the document at the collection via the service's <code>deleteDocumentFromCollection</code> method.</li>
<li>Getting the data at the document's path from the <code>fakeFirebaseFirestore</code>.</li>
<li>Asserting that the document does not exist.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart">test(<span class="hljs-string">'deleteDocumentFromCollection deletes a document from a given collection'</span>, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> FirestoreService firestoreService =
          FirestoreService(firestore: fakeFirebaseFirestore!);
      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

      <span class="hljs-keyword">final</span> CollectionReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; collectionReference =
          fakeFirebaseFirestore!.collection(collectionPath);

      <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
          <span class="hljs-keyword">await</span> collectionReference.add(data);

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> documentPath = documentReference.path;

      <span class="hljs-keyword">await</span> firestoreService.deleteDocumentFromCollection(
          collectionPath: collectionPath, documentPath: documentPath);

      <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentSnapshot =
          <span class="hljs-keyword">await</span> collectionReference.doc(documentPath).<span class="hljs-keyword">get</span>();

      expect(documentSnapshot.exists, <span class="hljs-keyword">false</span>); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
    });
</code></pre>
</li>
<li><h4 id="heading-test-for-getting-a-documents-data"><em>Test For Getting A Document's Data.</em></h4>
<pre><code class="lang-dart">Future&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getFromDocument(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath, <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
  <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).<span class="hljs-keyword">get</span>();
}
</code></pre>
<p> The <code>getFromDocument</code> method above can be tested by: </p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Setting data to the document via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Retrieving the actual data in the document via the service's <code>getFromDocument</code> method.</li>
<li>Asserting that the data retrieved via the service is equal to the data set via <code>fakeFirebaseFirestore</code>. </li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart">test(<span class="hljs-string">'getFromDocument gets data from a given document'</span>, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> FirestoreService firestoreService =
          FirestoreService(firestore: fakeFirebaseFirestore!);

      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

      <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
          fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

      <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">set</span>(data);

      <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedDocumentSnapshot =
          <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">get</span>();

      <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
          <span class="hljs-keyword">await</span> firestoreService.getFromDocument(
              collectionPath: collectionPath, documentPath: documentPath);

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? expectedData =
          expectedDocumentSnapshot.data();
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

      expect(actualData, expectedData); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
    });
</code></pre>
</li>
<li><h4 id="heading-test-for-setting-documentss-data"><em>Test For Setting Documents's Data.</em></h4>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; setDataOnDocument(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
  <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).<span class="hljs-keyword">set</span>(data);
}
</code></pre>
<p> The <code>setDataOnDocument</code> method above can be tested by: </p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Setting data to the document via the service's <code>setDataOnDocument</code> method.</li>
<li>Retrieving the actual data in the document via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Asserting that the data set via the service is equal to the data retrieved via <code>fakeFirebaseFirestore</code>.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart">test(<span class="hljs-string">'setDataOnDocument sets data on a given document'</span>, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> FirestoreService firestoreService =
          FirestoreService(firestore: fakeFirebaseFirestore!);

      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

      <span class="hljs-keyword">await</span> firestoreService.setDataOnDocument(
          data: data,
          collectionPath: collectionPath,
          documentPath: documentPath);

      <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
          fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

      <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
          <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">get</span>();
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

      <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedData = data;

      expect(actualData, expectedData); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
    });
</code></pre>
</li>
<li><h4 id="heading-test-for-getting-a-snapshot-stream-from-a-document"><em>Test For Getting A Snapshot Stream From A Document.</em></h4>
<pre><code class="lang-dart">Stream&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt; getSnapshotStreamFromDocument(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath, <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
  <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).snapshots();
}
</code></pre>
<p> The <code>getSnapshotStreamFromDocument</code> method above can be tested by: </p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Setting the data to the document via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Retrieving the expected snapshot stream from the <code>fakeFirebaseFirestore</code> object and the actual snapshot stream from the service's <code>getSnapshotStreamFromDocument</code> method.</li>
<li>Getting the data from the two snapshot streams.</li>
<li>Asserting that the actual data via the service is equal to the expected data via <code>fakeFirebaseFirestore</code>.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart">test(
        <span class="hljs-string">'getSnapshotStreamFromDocument returns a stream of DocumentSnapshot containing the data set'</span>,
        () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> FirestoreService firestoreService =
          FirestoreService(firestore: fakeFirebaseFirestore!);

      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
      <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

      <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
          fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

      <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">set</span>(data);

      <span class="hljs-keyword">final</span> Stream&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt;
          expectedSnapshotStream = documentReference.snapshots();

      <span class="hljs-keyword">final</span> Stream&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt;
          actualSnapshotStream =
          firestoreService.getSnapshotStreamFromDocument(
              collectionPath: collectionPath, documentPath: documentPath);

      <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedDocumentSnapshot =
          <span class="hljs-keyword">await</span> expectedSnapshotStream.first;
      <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
          <span class="hljs-keyword">await</span> actualSnapshotStream.first;

      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? expectedData =
          expectedDocumentSnapshot.data();
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

      expect(actualData, expectedData); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
    });
</code></pre>
</li>
<li><h4 id="heading-test-for-updating-a-documents-data"><em>Test For Updating A Document's Data.</em></h4>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; updateDataOnDocument(
    {<span class="hljs-keyword">required</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> collectionPath,
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">String</span> documentPath}) {
  <span class="hljs-keyword">return</span> firestore.collection(collectionPath).doc(documentPath).update(data);
}
</code></pre>
<p> The <code>updateDataOnDocument</code> method above can be tested by: </p>
<ul>
<li>Injecting the mock Firestore object, <code>fakeFirebaseFirestore</code>,  to the service.</li>
<li>Setting data to the document via the <code>fakeFirebaseFirestore</code> object.</li>
<li>Updating the data set to the document via the service's <code>updateDataOnDocument</code> method.</li>
<li>Retrieving the data in the document via <code>fakeFirebaseStore</code>.</li>
<li>Asserting that the updated data set via the service is equal to the expected data retrieved via <code>fakeFirebaseFirestore</code>.</li>
</ul>
<p>This is shown below:</p>
<pre><code class="lang-dart"> test(<span class="hljs-string">'updateDataOnDocument updates a given document\'s data'</span>, () <span class="hljs-keyword">async</span> {
   <span class="hljs-keyword">final</span> FirestoreService firestoreService =
          FirestoreService(firestore: fakeFirebaseFirestore!);

   <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
   <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

   <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
          fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

   <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">set</span>(data);

   <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataUpdate = {<span class="hljs-string">'data'</span>: <span class="hljs-string">'43'</span>};

   <span class="hljs-keyword">await</span> firestoreService.updateDataOnDocument(
          data: dataUpdate,
          collectionPath: collectionPath,
          documentPath: documentPath);

   <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
          <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">get</span>();

   <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

   <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedData = dataUpdate;

   expect(actualData, expectedData); <span class="hljs-comment">/// <span class="markdown">Passes ✅</span></span>
 });
</code></pre>
</li>
</ul>
<h1 id="heading-complete-test-file">Complete Test File</h1>
<p>The content of the entire test file is shown below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:cloud_firestore/cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:fake_cloud_firestore/fake_cloud_firestore.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:firestore_unit_test_flutter/firestore_service.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'FirestoreService'</span>, () {
    FakeFirebaseFirestore? fakeFirebaseFirestore;
    <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; data = {<span class="hljs-string">'data'</span>: <span class="hljs-string">'42'</span>};

    setUp(() {
      fakeFirebaseFirestore = FakeFirebaseFirestore();
    });

    group(
      <span class="hljs-string">'Collection Operations'</span>,
      () {
        test(<span class="hljs-string">'addToCollection adds data to given collection'</span>, () <span class="hljs-keyword">async</span> {
          <span class="hljs-keyword">final</span> FirestoreService firestoreService =
              FirestoreService(firestore: fakeFirebaseFirestore!);
          <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

          <span class="hljs-keyword">await</span> firestoreService.addToCollection(
              data: data, collectionPath: collectionPath);

          <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDataList =
              (<span class="hljs-keyword">await</span> fakeFirebaseFirestore!.collection(<span class="hljs-string">'collectionPath'</span>).<span class="hljs-keyword">get</span>())
                  .docs
                  .map((e) =&gt; e.data())
                  .toList();

          expect(actualDataList, <span class="hljs-keyword">const</span> MapListContains(data));
        });

        test(<span class="hljs-string">'getFromCollection gets data from a given collection'</span>, () <span class="hljs-keyword">async</span> {
          <span class="hljs-keyword">final</span> FirestoreService firestoreService =
              FirestoreService(firestore: fakeFirebaseFirestore!);
          <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

          <span class="hljs-keyword">await</span> fakeFirebaseFirestore!.collection(collectionPath).add(data);

          <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; dataList = (<span class="hljs-keyword">await</span> firestoreService
                  .getFromCollection(collectionPath: collectionPath))
              .docs
              .map((e) =&gt; e.data())
              .toList();

          expect(dataList, <span class="hljs-keyword">const</span> MapListContains(data));
        });

        test(
            <span class="hljs-string">'getSnapshotStreamFromCollection returns a stream of QuerySnaphot containing the data added'</span>,
            () <span class="hljs-keyword">async</span> {
          <span class="hljs-keyword">final</span> FirestoreService firestoreService =
              FirestoreService(firestore: fakeFirebaseFirestore!);
          <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

          <span class="hljs-keyword">final</span> CollectionReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; collectionReference =
              fakeFirebaseFirestore!.collection(collectionPath);
          <span class="hljs-keyword">await</span> collectionReference.add(data);

          <span class="hljs-keyword">final</span> Stream&lt;QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt;
              expectedSnapshotStream = collectionReference.snapshots();

          <span class="hljs-keyword">final</span> actualSnapshotStream = firestoreService
              .getSnapshotStreamFromCollection(collectionPath: collectionPath);

          <span class="hljs-keyword">final</span> QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedQuerySnapshot =
              <span class="hljs-keyword">await</span> expectedSnapshotStream.first;
          <span class="hljs-keyword">final</span> QuerySnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualQuerySnapshot =
              <span class="hljs-keyword">await</span> actualSnapshotStream.first;

          <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedDataList =
              expectedQuerySnapshot.docs.map((e) =&gt; e.data()).toList();
          <span class="hljs-keyword">final</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDataList =
              actualQuerySnapshot.docs.map((e) =&gt; e.data()).toList();

          expect(actualDataList, expectedDataList);
        });
      },
    );

    group(<span class="hljs-string">'Document Operations'</span>, () {
      test(
          <span class="hljs-string">'deleteDocumentFromCollection deletes a document from a given collection'</span>,
          () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;

        <span class="hljs-keyword">final</span> CollectionReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; collectionReference =
            fakeFirebaseFirestore!.collection(collectionPath);

        <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
            <span class="hljs-keyword">await</span> collectionReference.add(data);

        <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> documentPath = documentReference.path;

        <span class="hljs-keyword">await</span> firestoreService.deleteDocumentFromCollection(
            collectionPath: collectionPath, documentPath: documentPath);

        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentSnapshot =
            <span class="hljs-keyword">await</span> collectionReference.doc(documentPath).<span class="hljs-keyword">get</span>();

        expect(documentSnapshot.exists, <span class="hljs-keyword">false</span>);
      });

      test(<span class="hljs-string">'getFromDocument gets data from a given document'</span>, () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

        <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
            fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

        <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">set</span>(data);

        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedDocumentSnapshot =
            <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">get</span>();

        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
            <span class="hljs-keyword">await</span> firestoreService.getFromDocument(
                collectionPath: collectionPath, documentPath: documentPath);

        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? expectedData =
            expectedDocumentSnapshot.data();
        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

        expect(actualData, expectedData);
      });

      test(<span class="hljs-string">'setDataOnDocument sets data on a given document'</span>, () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

        <span class="hljs-keyword">await</span> firestoreService.setDataOnDocument(
            data: data,
            collectionPath: collectionPath,
            documentPath: documentPath);

        <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
            fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
            <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">get</span>();
        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedData = data;

        expect(actualData, expectedData);
      });

      test(
          <span class="hljs-string">'getSnapshotStreamFromDocument returns a stream of DocumentSnapshot containing the data set'</span>,
          () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

        <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
            fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

        <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">set</span>(data);

        <span class="hljs-keyword">final</span> Stream&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt;
            expectedSnapshotStream = documentReference.snapshots();

        <span class="hljs-keyword">final</span> Stream&lt;DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt;&gt;
            actualSnapshotStream =
            firestoreService.getSnapshotStreamFromDocument(
                collectionPath: collectionPath, documentPath: documentPath);

        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; expectedDocumentSnapshot =
            <span class="hljs-keyword">await</span> expectedSnapshotStream.first;
        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
            <span class="hljs-keyword">await</span> actualSnapshotStream.first;

        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? expectedData =
            expectedDocumentSnapshot.data();
        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

        expect(actualData, expectedData);
      });

      test(<span class="hljs-string">'updateDataOnDocument updates a given document\'s data'</span>, () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">final</span> FirestoreService firestoreService =
            FirestoreService(firestore: fakeFirebaseFirestore!);

        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> collectionPath = <span class="hljs-string">'collectionPath'</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> documentPath = <span class="hljs-string">'documentPath'</span>;

        <span class="hljs-keyword">final</span> DocumentReference&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; documentReference =
            fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);

        <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">set</span>(data);

        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; dataUpdate = {<span class="hljs-string">'data'</span>: <span class="hljs-string">'43'</span>};

        <span class="hljs-keyword">await</span> firestoreService.updateDataOnDocument(
            data: dataUpdate,
            collectionPath: collectionPath,
            documentPath: documentPath);

        <span class="hljs-keyword">final</span> DocumentSnapshot&lt;<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;&gt; actualDocumentSnapshot =
            <span class="hljs-keyword">await</span> documentReference.<span class="hljs-keyword">get</span>();

        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt;? actualData = actualDocumentSnapshot.data();

        <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">dynamic</span>&gt; expectedData = dataUpdate;

        expect(actualData, expectedData);
      });
    });
  });
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MapListContains</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Matcher</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">dynamic</span>, <span class="hljs-built_in">dynamic</span>&gt; _expected;

  <span class="hljs-keyword">const</span> MapListContains(<span class="hljs-keyword">this</span>._expected);

  <span class="hljs-meta">@override</span>
  Description describe(Description description) {
    <span class="hljs-keyword">return</span> description.add(<span class="hljs-string">'contains '</span>).addDescriptionOf(_expected);
  }

  <span class="hljs-meta">@override</span>
  <span class="hljs-built_in">bool</span> matches(<span class="hljs-built_in">dynamic</span> item, <span class="hljs-built_in">Map</span> matchState) {
    <span class="hljs-keyword">if</span> (item <span class="hljs-keyword">is</span> <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">Map</span>&gt;) {
      <span class="hljs-keyword">return</span> item.any((element) =&gt; mapEquals(element, _expected));
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
  }
}
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This article demonstrates how to unit test Firebase Firestore operations involving Collections and Documents. It also shows how to use a custom Matcher in test assertions.</p>
<p>The complete code can be found on <a target="_blank" href="https://github.com/victoreronmosele/firestore-unit-test-flutter">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Mocking and Testing Shared Preferences in Flutter Unit Tests]]></title><description><![CDATA[Introduction
This article demonstrates how to unit-test  Shared Preferences in Flutter projects.
This article assumes that the reader is familiar with Flutter development and the Dart language. 
The code in the demo was created using Flutter Channel ...]]></description><link>https://blog.victoreronmosele.com/mocking-shared-preferences-flutter</link><guid isPermaLink="true">https://blog.victoreronmosele.com/mocking-shared-preferences-flutter</guid><category><![CDATA[unit testing]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Victor Eronmosele]]></dc:creator><pubDate>Mon, 06 Dec 2021 10:48:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1638787624495/Ar1uW___JO.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>This article demonstrates how to unit-test  <a target="_blank" href="https://pub.dev/packages/shared_preferences">Shared Preferences</a> in Flutter projects.</p>
<p>This article assumes that the reader is familiar with Flutter development and the Dart language. </p>
<p>The code in the demo was created using <code>Flutter Channel stable, 2.5.3</code>.</p>
<h1 id="heading-problem-statement">Problem Statement</h1>
<p>We have a <code>SharedPreferencesUtil</code> class that makes use of the <code>SharedPreferences</code> object to store and retrieve an integer <code>count</code>. Our task is to unit-test the methods of this class.</p>
<p>The class is shown below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences/shared_preferences.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SharedPreferencesUtil</span> </span>{
  SharedPreferencesUtil({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.sharedPreferences});

  <span class="hljs-keyword">final</span> SharedPreferences sharedPreferences;

  <span class="hljs-meta">@visibleForTesting</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">const</span> <span class="hljs-built_in">String</span> countKey = <span class="hljs-string">'counter'</span>;

  <span class="hljs-built_in">int</span> getCount() {
    <span class="hljs-keyword">return</span> sharedPreferences.getInt(countKey) ?? <span class="hljs-number">0</span>;
  }

  Future&lt;<span class="hljs-built_in">bool</span>&gt; setCount(<span class="hljs-built_in">int</span> count) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> sharedPreferences.setInt(countKey, count);
  }
}
</code></pre>
<h1 id="heading-testing-the-methods">Testing the methods</h1>
<p>In the <code>SharedPreferencesUtil</code> class above, the <code>SharedPreferences</code> object is passed through the constructor and it is used in the following methods:</p>
<ul>
<li><code>getCount()</code>:  To return the value of the count integer stored in <code>SharedPreferences</code> with the <code>countKey</code> key and return 0 if the value in <code>SharedPreferences</code> is <code>null</code></li>
<li><code>setCount()</code>: To set the value of count int with the <code>countKey</code> key in <code>SharedPreferences</code></li>
</ul>
<p>In order to write a test, we need to mock the <code>SharedPreferences</code> object that we pass into the <code>SharedPreferencesUtil</code> class.</p>
<p>This is done using the <a target="_blank" href="https://pub.dev/documentation/shared_preferences/latest/shared_preferences/SharedPreferences/setMockInitialValues.html">SharedPreferences.setMockInitialValues()</a> static method.</p>
<h2 id="heading-setmockinitialvalues">setMockInitialValues()</h2>
<blockquote>
<p>Initializes the shared preferences with mock values for testing.</p>
<p><a target="_blank" href="https://pub.dev/documentation/shared_preferences/latest/shared_preferences/SharedPreferences/setMockInitialValues.html">Source</a> </p>
</blockquote>
<p>This method is used to mock the initial state of the <code>SharedPreferences</code> object used in tests. It takes a parameter of type <code>Map&lt;String, Object&gt;</code> for which the key is used to identify the stored <code>SharedPreferences</code> object and the value is the actual object stored in <code>SharedPreferences</code>.</p>
<p>For instance:</p>
<p>If we want our <code>SharedPreferences</code> object in our test to store the number 42 with the key <code>counter</code>, we do it this way:</p>
<pre><code class="lang-dart">SharedPreferences.setMockInitialValues({<span class="hljs-string">'counter'</span>: <span class="hljs-number">42</span>});
</code></pre>
<h2 id="heading-testing-the-getcounter-method">Testing the getCounter() method</h2>
<ul>
<li><p><em>Test that the <code>getCount()</code> method returns 0 if there is no value stored in <code>SharedPreferences</code>.</em></p>
<p>  We first call the <code>SharedPreferences.setMockInitialValues()</code> and pass it <code>{}</code> (empty <code>Map</code> object) method to make sure nothing is stored in <code>SharedPreferences</code>. We, then, pass it into the <code>SharedPreferencesUtil</code> class. </p>
<p>  We, then, can call the <code>getCount()</code> method and compare it with the expected value of 0 using the  <a target="_blank" href="https://pub.dev/documentation/test_api/latest/expect/expect.html">expect()</a>  method from the test library.</p>
</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences/shared_preferences.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences_unit_tests_flutter/shared_preferences_util.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

main() {
  group(SharedPreferencesUtil, () {
    group(<span class="hljs-string">'getCounter'</span>, () {
      test(<span class="hljs-string">'returns 0 if no value is stored in SharedPreferences'</span>, () async {
        SharedPreferences.setMockInitialValues({});

        final SharedPreferences sharedPreferences <span class="hljs-operator">=</span>
            await SharedPreferences.getInstance();

        final SharedPreferencesUtil sharedPreferencesUtil <span class="hljs-operator">=</span>
            SharedPreferencesUtil(sharedPreferences: sharedPreferences);

        const expectedCounter <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
        final actualCounter <span class="hljs-operator">=</span> sharedPreferencesUtil.getCount();

        expect(expectedCounter, actualCounter);
      });
    });
  });
}
</code></pre><ul>
<li><p><em>Test that the <code>getCount()</code> method returns the correct value stored in <code>SharedPreferences</code>.</em></p>
<p>  We first call <code>SharedPreferences.setMockInitialValues()</code> and pass it <code>{SharedPreferencesUtil.countKey: counterValue}</code> where <code>SharedPreferencesUtil.counterKey</code> is the key and <code>counterValue</code> contains the value we want to store. We, then, pass it into the <code>SharedPreferencesUtil</code> class. </p>
<p>  We, then, can call the <code>getCount()</code> method and compare it with the expected value of 0 using the <code>expect()</code> method.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences/shared_preferences.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences_unit_tests_flutter/shared_preferences_util.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

main() {
  group(SharedPreferencesUtil, () {
    group(<span class="hljs-string">'getCounter'</span>, () {
      test(<span class="hljs-string">'returns 0 if no value is stored in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
       ...
      });

      test(<span class="hljs-string">'returns the correct value stored in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">const</span> counterValue = <span class="hljs-number">42</span>;
        SharedPreferences.setMockInitialValues(
            {SharedPreferencesUtil.countKey: counterValue});
        <span class="hljs-keyword">final</span> SharedPreferences sharedPreferences =
            <span class="hljs-keyword">await</span> SharedPreferences.getInstance();

        <span class="hljs-keyword">final</span> SharedPreferencesUtil sharedPreferencesUtil =
            SharedPreferencesUtil(sharedPreferences: sharedPreferences);

        <span class="hljs-keyword">const</span> expectedCount = counterValue;
        <span class="hljs-keyword">final</span> actualCounter = sharedPreferencesUtil.getCount();
        expect(expectedCount, actualCounter);
      });
    });
  });
}
</code></pre>
<h2 id="heading-testing-the-setcounter-method">Testing the setCounter() method</h2>
<ul>
<li><p><em>Test that the <code>setCount()</code> method stores the correct value in <code>SharedPreferences</code>.</em></p>
<p>  We first call the <code>SharedPreferences.setMockInitialValues()</code> and pass it <code>{}</code> (empty <code>Map</code> object) method to make sure nothing is stored in <code>SharedPreferences</code>. We, then, pass it into the <code>SharedPreferencesUtil</code> class. </p>
<p>  We, then, can call the <code>SharedPreferences.getInt()</code> method, pass the <code>SharedPreferencesUtil.countKey</code> key to it to get the value in <code>SharedPreferences</code>  compare it with the expected value in the <code>countValue</code> variable.</p>
</li>
</ul>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences/shared_preferences.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences_unit_tests_flutter/shared_preferences_util.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

main() {
  group(SharedPreferencesUtil, () {
    group(<span class="hljs-string">'getCounter'</span>, () {
      test(<span class="hljs-string">'returns 0 if no value is stored in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        ...
      });

      test(<span class="hljs-string">'returns the correct value stored in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        ...
      });
    });

    group(<span class="hljs-string">'setCounter'</span>, () {
      test(<span class="hljs-string">'stores the correct count value in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        SharedPreferences.setMockInitialValues({});
        <span class="hljs-keyword">final</span> SharedPreferences sharedPreferences =
            <span class="hljs-keyword">await</span> SharedPreferences.getInstance();

        <span class="hljs-keyword">final</span> SharedPreferencesUtil sharedPreferencesUtil =
            SharedPreferencesUtil(sharedPreferences: sharedPreferences);
        <span class="hljs-keyword">const</span> countValue = <span class="hljs-number">42</span>;

        sharedPreferencesUtil.setCount(countValue);
        <span class="hljs-keyword">const</span> expectedCounter = countValue;
        <span class="hljs-keyword">final</span> actualCounter =
            sharedPreferences.getInt(SharedPreferencesUtil.countKey);

        expect(expectedCounter, actualCounter);
      });
    });
  });
}
</code></pre>
<h2 id="heading-complete-test-file">Complete Test File</h2>
<p>The content of the entire test file is shown below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences/shared_preferences.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shared_preferences_unit_tests_flutter/shared_preferences_util.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

main() {
  group(SharedPreferencesUtil, () {
    group(<span class="hljs-string">'getCounter'</span>, () {
      test(<span class="hljs-string">'returns 0 if no value is stored in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        SharedPreferences.setMockInitialValues({});

        <span class="hljs-keyword">final</span> SharedPreferences sharedPreferences =
            <span class="hljs-keyword">await</span> SharedPreferences.getInstance();

        <span class="hljs-keyword">final</span> SharedPreferencesUtil sharedPreferencesUtil =
            SharedPreferencesUtil(sharedPreferences: sharedPreferences);

        <span class="hljs-keyword">const</span> expectedCounter = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">final</span> actualCounter = sharedPreferencesUtil.getCount();

        expect(expectedCounter, actualCounter);
      });

      test(<span class="hljs-string">'returns the correct value stored in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">const</span> counterValue = <span class="hljs-number">42</span>;
        SharedPreferences.setMockInitialValues(
            {SharedPreferencesUtil.countKey: counterValue});
        <span class="hljs-keyword">final</span> SharedPreferences sharedPreferences =
            <span class="hljs-keyword">await</span> SharedPreferences.getInstance();

        <span class="hljs-keyword">final</span> SharedPreferencesUtil sharedPreferencesUtil =
            SharedPreferencesUtil(sharedPreferences: sharedPreferences);

        <span class="hljs-keyword">const</span> expectedCount = counterValue;
        <span class="hljs-keyword">final</span> actualCounter = sharedPreferencesUtil.getCount();
        expect(expectedCount, actualCounter);
      });
    });

    group(<span class="hljs-string">'setCounter'</span>, () {
      test(<span class="hljs-string">'stores the correct count value in SharedPreferences'</span>, () <span class="hljs-keyword">async</span> {
        SharedPreferences.setMockInitialValues({});
        <span class="hljs-keyword">final</span> SharedPreferences sharedPreferences =
            <span class="hljs-keyword">await</span> SharedPreferences.getInstance();

        <span class="hljs-keyword">final</span> SharedPreferencesUtil sharedPreferencesUtil =
            SharedPreferencesUtil(sharedPreferences: sharedPreferences);
        <span class="hljs-keyword">const</span> countValue = <span class="hljs-number">42</span>;

        sharedPreferencesUtil.setCount(countValue);
        <span class="hljs-keyword">const</span> expectedCounter = countValue;
        <span class="hljs-keyword">final</span> actualCounter =
            sharedPreferences.getInt(SharedPreferencesUtil.countKey);

        expect(expectedCounter, actualCounter);
      });
    });
  });
}
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Mocking the <code>SharedPreferences</code> object in tests makes use of the <code>setMockInitialValues</code> method to initialize the state. </p>
<p>The complete code can be found on <a target="_blank" href="https://github.com/victoreronmosele/shared-preferences-unit-test-flutter">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Mocking and Testing The File System In Dart/Flutter Unit Tests]]></title><description><![CDATA[This article will describe how to mock and test the file system in Dart/Flutter for tests and in particular unit tests.
This article assumes that the reader is familiar with Flutter development and the Dart language.
The article is divided into:

Int...]]></description><link>https://blog.victoreronmosele.com/mocking-filesystems-dart</link><guid isPermaLink="true">https://blog.victoreronmosele.com/mocking-filesystems-dart</guid><category><![CDATA[Flutter]]></category><category><![CDATA[Testing]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Victor Eronmosele]]></dc:creator><pubDate>Sat, 09 Oct 2021 19:47:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1633767608602/vq1yGMPhL.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article will describe how to mock and test the file system in Dart/Flutter for tests and in particular unit tests.</p>
<p>This article assumes that the reader is familiar with Flutter development and the Dart language.</p>
<p>The article is divided into:</p>
<ol>
<li><a class="post-section-overview" href="#introduction">Introduction</a></li>
<li><a class="post-section-overview" href="#dartio">Using the dart:io library</a></li>
<li><a class="post-section-overview" href="#file">Using the file library</a></li>
<li><a class="post-section-overview" href="#conclusion">Conclusion</a></li>
</ol>
<h2 id="lessspan-idintroductiongreater1-introductionlessspangreater"><span id="introduction">1. Introduction</span></h2>
<p>We want to create a function <code>createHelloWorldFile</code> that creates a file <code>hello_world.dart</code> at a location <code>generated/hello_world.dart</code>. </p>
<p>We also want to unit test this function to ensure that the file is created at the specific location when the function runs.</p>
<p>We'll look at two different ways of doing this:</p>
<ul>
<li><a class="post-section-overview" href="#dartio">Using the dart:io library</a></li>
<li><a class="post-section-overview" href="#file">Using the file library</a></li>
</ul>
<h2 id="lessspan-iddartiogreater2-using-the-dartio-librarylessspangreater"><span id="dartio">2. Using the dart:io library</span></h2>
<h3 id="writing-the-function">Writing the Function</h3>
<p>This implementation is to make use of the <a target="_blank" href="https://api.dart.dev/dart-io/dart-io-library.html">dart:io</a> library and its <a target="_blank" href="https://api.dart.dev/stable/dart-io/File-class.html">File</a> object to create the file.</p>
<p>We first create the <code>File</code> object with the desired location passed as a parameter and call <code>.createSync</code> to create the file. </p>
<p><code>recursive:true</code> is needed to ensure that any non-existing path is created first before creating the file.</p>
<p>Here is the code for the function:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>;

<span class="hljs-keyword">void</span> createHelloWorldFile() {
  <span class="hljs-keyword">final</span> file = File(<span class="hljs-string">'generated/hello_world.dart'</span>);
  file.createSync(recursive: <span class="hljs-keyword">true</span>);
}
</code></pre>
<h3 id="calling-the-function">Calling the Function</h3>
<p>We can call the function like this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>;

<span class="hljs-keyword">void</span> main(<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; arguments) {
  createHelloWorldFile();
}
</code></pre>
<h3 id="testing-the-function">Testing the Function</h3>
<p>The first step is to add the test library to <code>dev_dependencies</code> in the <code>pubspec.yaml</code> file like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-attr">test:</span>
</code></pre>
<p>The next step is to write the test for the function like this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  test(<span class="hljs-string">'createHelloWorldFile creates a file at generated/hello_world.dart'</span>, () {
    createHelloWorldFile();
    <span class="hljs-keyword">final</span> file = File(<span class="hljs-string">'generated/hello_world.dart'</span>);
    expect(file.existsSync(), <span class="hljs-keyword">true</span>);
  });
}
</code></pre>
<p>The test code above uses the <a target="_blank" href="https://api.dart.dev/stable/2.14.3/dart-io/FileSystemEntity/existsSync.html">.existsSync</a> property on the created <code>File</code> object to confirm if it does exist.</p>
<p>The test passes when it is run.</p>
<h3 id="the-problem-with-the-test">The Problem with the Test</h3>
<p>Running this test passes but the problem with this test is that it actually creates the <code>generated/hello_world.dart</code> file. </p>
<p>This generated file is not needed for the actual test so it would be better to use a mock file system so we can write/run as many tests as we want without worrying about side effects.</p>
<h2 id="lessspan-idfilegreater3-using-the-file-librarylessspangreater"><span id="file">3. Using the file library</span></h2>
<h3 id="writing-the-function">Writing the Function</h3>
<p>To solve the problem with the extra file generated, we will use the <a target="_blank" href="https://pub.dev/packages/file">file</a> library which gives the ability to create custom file systems and in-memory file systems for easy unit testing of code that works with file systems.</p>
<p>The first step is to add the <code>file</code> library to <code>dependecies</code> in our <code>pubspec.yaml</code> file like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">file:</span>
</code></pre>
<p>The next step is to update our <code>createHelloWorldFile</code> function to this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:file/file.dart'</span>;

<span class="hljs-keyword">void</span> createHelloWorldFile(FileSystem fileSystem) {
  <span class="hljs-keyword">final</span> file = fileSystem.file(<span class="hljs-string">'generated/hello_world.dart'</span>);
  file.createSync(recursive: <span class="hljs-keyword">true</span>);
}
</code></pre>
<p>In the code above, we inject the fileSystem into the function <code>createHelloWorldFile</code> and use that to create the file.</p>
<h3 id="calling-the-function">Calling the Function</h3>
<p>To call the function, we pass a <a target="_blank" href="https://pub.dev/documentation/file/latest/local/LocalFileSystem-class.html">LocalFileSystem</a> object into the <code>createHelloWorldFile</code> function.</p>
<blockquote>
<p>LocalFileSystem class </p>
<p>A wrapper implementation around dart:io's implementation.</p>
<p>Since this implementation of the FileSystem interface delegates to dart:io, [it] is not suitable for use in the browser.</p>
</blockquote>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:file/local.dart'</span>;

<span class="hljs-keyword">void</span> main(<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; arguments) {
  <span class="hljs-keyword">final</span> localFileSystem = LocalFileSystem();
  createHelloWorldFile(localFileSystem);
}
</code></pre>
<h3 id="testing-the-function">Testing the Function</h3>
<p>In order to test the function, we can pass a <a target="_blank" href="https://pub.dev/documentation/file/latest/memory/MemoryFileSystem-class.html">MemoryFileSystem</a> object into the <code>createHelloWorldFile</code>.</p>
<blockquote>
<p>MemoryFileSystem class </p>
<p>An implementation of FileSystem that exists entirely in memory with an internal representation loosely based on the Filesystem Hierarchy Standard.</p>
<p>MemoryFileSystem is suitable for mocking and tests, as well as for caching or staging before writing or reading to a live system.</p>
</blockquote>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:file/memory.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> main() {
  test(<span class="hljs-string">'createHelloWorldFile creates a file at generated/hello_world.dart'</span>, () {
    <span class="hljs-keyword">final</span> memoryFileSystem = MemoryFileSystem();
    createHelloWorldFile(memoryFileSystem);

    <span class="hljs-keyword">final</span> file = memoryFileSystem.file(<span class="hljs-string">'generated/hello_world.dart'</span>);

    expect(file.existsSync(), <span class="hljs-keyword">true</span>);
  });
}
</code></pre>
<p>Running this test passes and it does not create any extra files.</p>
<h2 id="lessspan-idconclusiongreater4-conclusionlessspangreater"><span id="conclusion">4. Conclusion</span></h2>
<p>The <a target="_blank" href="https://api.dart.dev/stable/dart-io/File-class.html">file</a> library provides an easy way to test operations involving files with the use of the <code>MemoryFileSystem</code> which is an in-memory file system that does not write to the actual file system. </p>
<p>The complete project can be found on <a target="_blank" href="https://github.com/victoreronmosele/file-system-test-dart">Github</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Mocking callbacks in Dart/Flutter unit tests]]></title><description><![CDATA[This article is a guide on how to mock callback functions and verify that the callback functions are called in Dart/Flutter unit tests.
The article is divided into:

Introduction
Mocking The Callback
Testing The Callback

1. Introduction
Let's say we...]]></description><link>https://blog.victoreronmosele.com/mocking-callbacks-dart</link><guid isPermaLink="true">https://blog.victoreronmosele.com/mocking-callbacks-dart</guid><category><![CDATA[Testing]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Dart]]></category><dc:creator><![CDATA[Victor Eronmosele]]></dc:creator><pubDate>Fri, 24 Sep 2021 14:52:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1632495208967/e15Bt_6n_.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article is a guide on how to mock callback functions and verify that the callback functions are called in Dart/Flutter unit tests.</p>
<p>The article is divided into:</p>
<ol>
<li><a class="post-section-overview" href="#introduction">Introduction</a></li>
<li><a class="post-section-overview" href="#mocking">Mocking The Callback</a></li>
<li><a class="post-section-overview" href="#testing">Testing The Callback</a></li>
</ol>
<h2 id="heading-1-introduction"><span id="introduction">1. Introduction</span></h2>
<p>Let's say we have a function <code>readDate</code> with the definition below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">void</span> readData(<span class="hljs-keyword">void</span> <span class="hljs-built_in">Function</span>() onDataReadComplete) {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Reading data'</span>);
    onDataReadComplete();
}
</code></pre>
<p>The function takes a callback <code>onDataReadComplete</code> as a lone  <a target="_blank" href="https://dart.dev/guides/language/language-tour#parameters">positional parameter</a> and it calls <code>onDataReadComplete</code> after it prints ('Reading data').</p>
<p>Our task here is to write a unit test to confirm that <code>onDataReadComplete</code> is run whenever <code>readData</code> runs.</p>
<p>In order to do this we need to mock <code>onDataReadComplete</code> and confirm that it is actually called.</p>
<h2 id="heading-2-mocking-the-callback"><span id="mocking"> 2. Mocking The Callback</span></h2>
<p>In other to mock the callback, we will use the mock library <a target="_blank" href="https://pub.dev/packages/mockito">mockito</a>.</p>
<p>The first step is to add the <code>mockito</code> dependecy to our project by including it in the <code>pubspec.yaml</code> file. We will also add the <a target="_blank" href="https://pub.dev/packages/test">test</a> library to the <code>pubspec.yaml</code> file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-attr">mockito:</span>
  <span class="hljs-attr">test:</span>
</code></pre>
<p>Mockito has two ways to generate mock objects:</p>
<ol>
<li><a target="_blank" href="https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#code-generation">Code Generation</a> </li>
<li><a target="_blank" href="https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#manual-mock-implementaion">Manual Mock Implementation</a> </li>
</ol>
<p>For this article, we will use the manual mock implementation for simplicity.</p>
<p>A class can be mocked manually by creating a class that extends Mock and implements the class to be mocked. </p>
<p>Below is a sample from the documentation:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockHttpServer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">HttpServer</span> </span>{}
</code></pre>
<p>This means we need a class to represent our function so our mock class can implement it.</p>
<p>We will make use of Dart's  <a target="_blank" href="https://dart.dev/guides/language/language-tour#callable-classes">Callable Classes</a>.</p>
<blockquote>
<p>To allow an instance of your Dart class to be called like a function, implement the call() method.</p>
<p>- <a target="_blank" href="https://dart.dev/guides/language/language-tour#callable-classes">Callable Classes</a></p>
</blockquote>
<p>This means we can represent our <code>void Function() onDataReadComplete</code> function as this below:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OnDataReadComplete</span> </span>{
    <span class="hljs-keyword">void</span> call();
}
</code></pre>
<p>The class is an <code>abstract</code> class since we won't be providing an implementation of the <code>call</code> method
.</p>
<p>The <code>call</code> method must have the same definition as the function to be mocked. This is why it is a <code>void</code> method.</p>
<p>Now, we can create our mock class like this below:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockOnDataReadComplete</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OnDataReadComplete</span> </span>{}
</code></pre>
<p>We can now, use <code>MockOnDataReadComplete</code> in our test.</p>
<h2 id="heading-3-testing-the-callback"><span id="testing">3. Testing The Callback</span></h2>
<p>We will follow the following steps in testing the callback function:</p>
<ol>
<li>Create the mock object <code>mockOnDataReadComplete</code> using <code>MockOnDataReadComplete</code>.</li>
<li>Run the <code>readData</code> function with <code>mockOnDataReadComplete</code> passed as a parameter.</li>
<li>Use the <code>verify</code> function to confirm that <code>mockOnDataReadComplete()</code> is called once. <em>Note that you need to call the function like this <code>mockOnDataReadComplete()</code> and not just pass the variable <code>mockOnDataReadComplete</code> to the verify function</em></li>
</ol>
<pre><code class="lang-dart">test(<span class="hljs-string">'onDataReadComplete gets called whenever readData runs'</span>, () {
    <span class="hljs-comment">//Creates the mock object</span>
    <span class="hljs-keyword">final</span> mockOnDataReadComplete = MockOnDataReadComplete();

    <span class="hljs-comment">//Runs the function</span>
    readData(mockOnDataReadComplete);

    <span class="hljs-comment">//Confirm that the mock object is called</span>
    verify(mockOnDataReadComplete()).called(<span class="hljs-number">1</span>);
  });
</code></pre>
<p>Here is the full test file:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:mockito/mockito.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">void</span> readData(<span class="hljs-keyword">void</span> <span class="hljs-built_in">Function</span>() onDataReadComplete) {
  <span class="hljs-built_in">print</span>(<span class="hljs-string">'Reading data'</span>);
  onDataReadComplete();
}

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OnDataReadComplete</span> </span>{
  <span class="hljs-keyword">void</span> call();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockOnDataReadComplete</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">OnDataReadComplete</span> </span>{}

<span class="hljs-keyword">void</span> main() {
  test(<span class="hljs-string">'onDataReadComplete gets called whenever readData runs'</span>, () {
    <span class="hljs-comment">//Creates the mock object</span>
    <span class="hljs-keyword">final</span> mockOnDataReadComplete = MockOnDataReadComplete();

    <span class="hljs-comment">//Runs the function</span>
    readData(mockOnDataReadComplete);

    <span class="hljs-comment">//Confirm that the mock object is called</span>
    verify(mockOnDataReadComplete()).called(<span class="hljs-number">1</span>);
  });
}
</code></pre>
<p>And now we run our tests and it passes!</p>
<p>The complete project can be found on  <a target="_blank" href="https://github.com/victoreronmosele/callback-test-dart">Github</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Rebuilding The Flutter Counter App In Jetpack Compose]]></title><description><![CDATA[This article will show how to rebuild the default Flutter counter app using Jetpack Compose.
The article is divided into two sections:

UI, where the app's user interface will be built.

State, where the state of the app's counter will be implemented...]]></description><link>https://blog.victoreronmosele.com/flutter-counter-in-compose</link><guid isPermaLink="true">https://blog.victoreronmosele.com/flutter-counter-in-compose</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Victor Eronmosele]]></dc:creator><pubDate>Fri, 06 Aug 2021 20:09:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628280317913/PKjye24Pq.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article will show how to rebuild the default <a target="_blank" href="https://flutter.dev/">Flutter</a> counter app using <a target="_blank" href="https://developer.android.com/jetpack/compose">Jetpack Compose</a>.</p>
<p>The article is divided into two sections:</p>
<ol>
<li><p><a class="post-section-overview" href="#ui">UI</a>, where the app's user interface will be built.</p>
</li>
<li><p><a class="post-section-overview" href="#state">State</a>, where the state of the app's counter will be implemented.</p>
</li>
</ol>
<p>Below is a screenshot of the default Flutter counter app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628273185586/9AM_bWM-k.jpeg" alt="162827266517692828 (3).jpeg" /></p>
<p>This is what we'll rebuild with Jetpack Compose.</p>
<p>The first step is to download a <a target="_blank" href="https://developer.android.com/studio">Jetpack Compose supported Android Studio</a> version (at this time of writing, that is the Arctic Fox version).</p>
<p>The next step is to create an empty compose activity by clicking on File &gt; New Project and selecting <code>Empty Compose Activity</code> from the templates shown.</p>
<p>Click on <code>Next</code>.</p>
<p>Then we enter our application name. For this article, we'll use "ComposeCounterApp".</p>
<p>Click on <code>Finish</code> and it should open the <code>ComposeCounterApp</code> project.</p>
<h2 id="heading-1-ui">1. <span id="ui">UI </span></h2>
<p>Open the <code>MainActivity.kt</code> file. It should contain the default Compose Activity code.</p>
<p>We'll remove the code and update the content of <code>MainActivity.kt</code> to this below:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> com.example.composecounterapp

<span class="hljs-keyword">import</span> android.os.Bundle
<span class="hljs-keyword">import</span> androidx.activity.ComponentActivity
<span class="hljs-keyword">import</span> androidx.activity.compose.setContent
<span class="hljs-keyword">import</span> androidx.compose.material.Scaffold
<span class="hljs-keyword">import</span> androidx.compose.runtime.Composable
<span class="hljs-keyword">import</span> com.example.composecounterapp.ui.theme.ComposeCounterAppTheme

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">ComponentActivity</span></span>() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        setContent {
            ComposeCounterAppTheme {
                ComposeCounter()
            }
        }
    }
}

<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    Scaffold(
        content = {}
    )
}
</code></pre>
<p>On running the project on a device, an empty screen is displayed. This is because <code>ComposeCounter</code> only contains a <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#Scaffold">Scaffold</a> composable with empty content.</p>
<p>Next, we'll add the code for the app bar. We'll use the <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#TopAppBar">TopAppBar</a> composable and specify the title, the background colour, and the content colour.</p>
<p>Update the <code>ComposeCounter</code> function to this.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> primaryColor = Color(<span class="hljs-number">0xFF2196F3</span>)

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = <span class="hljs-string">"Compose Home Demo Page"</span>) },
                backgroundColor = primaryColor,
                contentColor = Color.White
            )
        },
        content = {}
    )
}
</code></pre>
<p>This will show the following screen:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628272825854/iNS_NCNHS.jpeg" alt="162827266517692828.jpeg" /></p>
<p>Next, we'll add the content of the screen. And for this, we'll pass a Column with two Texts to the Scaffold as <code>content</code>. The column items will be aligned in the center and we'll pass <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier">modifiers</a> to ensure the Column fills the screen horizontally and vertically.</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> primaryColor = Color(<span class="hljs-number">0xFF2196F3</span>)

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = <span class="hljs-string">"Compose Home Demo Page"</span>) },
                backgroundColor = primaryColor,
                contentColor = Color.White
            )
        },
        content = {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxHeight()
                    .fillMaxWidth()
            ) {
                Text(
                    text = <span class="hljs-string">"You have pushed this button this many times"</span>,
                )
                Text(
                    text = <span class="hljs-string">"0"</span>,
                    style = MaterialTheme.typography.h4,
                )
            }
        },
    )
}
</code></pre>
<p>And now we'll have this showing on our device.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628272968634/ZF1NNk3dE.jpeg" alt="162827266517692828 (1).jpeg" /></p>
<p>The last thing to add for the UI is the floating action button, which we can specify on the Scaffold by passing a <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#FloatingActionButton">FloatingActionButton</a> composable to the Scaffold as <code>floatingActionButton</code>.</p>
<p>We do that by updating the <code>ComposeCounter</code> composable to the code below:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> primaryColor = Color(<span class="hljs-number">0xFF2196F3</span>)

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = <span class="hljs-string">"Compose Home Demo Page"</span>) },
                backgroundColor = primaryColor,
                contentColor = Color.White
            )
        },
        content = {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxHeight()
                    .fillMaxWidth()
            ) {
                Text(
                    text = <span class="hljs-string">"You have pushed this button this many times"</span>,
                )
                Text(
                    text = <span class="hljs-string">"0"</span>,
                    style = MaterialTheme.typography.h4,
                )
            }
        },
        floatingActionButton = {
            FloatingActionButton(
                onClick = {},
                backgroundColor = primaryColor,
                contentColor = Color.White
            ) {
                Icon(Icons.Filled.Add, contentDescription = <span class="hljs-string">"Add button"</span>)
            }
        },
    )
}
</code></pre>
<p>And now, we'll have our complete UI! 🎉</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628273060642/L88MXLIwy.jpeg" alt="162827266517692828 (2).jpeg" /></p>
<h2 id="heading-2-state">2. State</h2>
<p>Currently, the app displays "0" as the counter state and we need to make sure that clicking the floating action button increments that state.</p>
<p>We need a variable to hold the state of the counter.</p>
<p>From <a target="_blank" href="https://developer.android.com/jetpack/compose/state">State and Jetpack Compose</a>,</p>
<blockquote>
<p>There are three ways to declare a MutableState object in a composable:</p>
<ul>
<li><p>val mutableState = remember { mutableStateOf(default) }</p>
</li>
<li><p>var value by remember { mutableStateOf(default) }</p>
</li>
<li><p>val (value, setValue) = remember { mutableStateOf(default) }</p>
</li>
</ul>
</blockquote>
<p>We'll be using the second one.</p>
<p>At the top of the <code>ComposeCounter</code> composable, we'll add a variable <code>counter</code> to hold the state of the counter and set its default value to zero.</p>
<p>We'll also use this <code>counter</code> variable in the Text composable instead of "0".</p>
<p>And now the <code>ComposeCounter</code> will have the following code:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> primaryColor = Color(<span class="hljs-number">0xFF2196F3</span>)
    <span class="hljs-keyword">var</span> counter: <span class="hljs-built_in">Int</span> <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }

    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = <span class="hljs-string">"Compose Home Demo Page"</span>) },
                backgroundColor = primaryColor,
                contentColor = Color.White
            )
        },
        content = {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxHeight()
                    .fillMaxWidth()
            ) {
                Text(
                    text = <span class="hljs-string">"You have pushed this button this many times"</span>,
                )
                Text(
                    text = counter.toString(),
                    style = MaterialTheme.typography.h4,
                )
            }
        },
        floatingActionButton = {
            FloatingActionButton(
                onClick = {
                },
                backgroundColor = primaryColor,
                contentColor = Color.White
            ) {
                Icon(Icons.Filled.Add, contentDescription = <span class="hljs-string">"Add button"</span>)
            }
        },
    )
}
</code></pre>
<p>There will be no visible change on the screen for now.</p>
<p>The next thing to do is to increment the counter when the floating action button is clicked.</p>
<p>We can do that in the <code>onClick</code> callback of the <code>FloatingActionButton</code>.</p>
<p>The code will be updated to this:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> primaryColor = Color(<span class="hljs-number">0xFF2196F3</span>)
    <span class="hljs-keyword">var</span> counter: <span class="hljs-built_in">Int</span> <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }


    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = <span class="hljs-string">"Compose Home Demo Page"</span>) },
                backgroundColor = primaryColor,
                contentColor = Color.White
            )
        },
        content = {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxHeight()
                    .fillMaxWidth()
            ) {
                Text(
                    text = <span class="hljs-string">"You have pushed this button this many times"</span>,
                )
                Text(
                    text = counter.toString(),
                    style = MaterialTheme.typography.h4,
                )
            }
        },
        floatingActionButton = {
            FloatingActionButton(
                onClick = {
                    counter += <span class="hljs-number">1</span>
                },
                backgroundColor = primaryColor,
                contentColor = Color.White
            ) {
                Icon(Icons.Filled.Add, contentDescription = <span class="hljs-string">"Add button"</span>)
            }
        },
    )
}
</code></pre>
<p>Now the app increments the counter when the floating action button is tapped as seen in the gif below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628273301925/_R-MgXVVO.gif" alt="162827266517692828.gif" /></p>
<p>And now the app is complete.</p>
<p>The complete code for the <code>MainActivity.kt</code> is this below:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> com.example.composecounterapp

<span class="hljs-keyword">import</span> android.os.Bundle
<span class="hljs-keyword">import</span> androidx.activity.ComponentActivity
<span class="hljs-keyword">import</span> androidx.activity.compose.setContent
<span class="hljs-keyword">import</span> androidx.compose.foundation.layout.Arrangement
<span class="hljs-keyword">import</span> androidx.compose.foundation.layout.Column
<span class="hljs-keyword">import</span> androidx.compose.foundation.layout.fillMaxHeight
<span class="hljs-keyword">import</span> androidx.compose.foundation.layout.fillMaxWidth
<span class="hljs-keyword">import</span> androidx.compose.material.*
<span class="hljs-keyword">import</span> androidx.compose.material.icons.Icons
<span class="hljs-keyword">import</span> androidx.compose.material.icons.filled.Add
<span class="hljs-keyword">import</span> androidx.compose.runtime.*
<span class="hljs-keyword">import</span> androidx.compose.ui.Alignment
<span class="hljs-keyword">import</span> androidx.compose.ui.Modifier
<span class="hljs-keyword">import</span> androidx.compose.ui.graphics.Color
<span class="hljs-keyword">import</span> com.example.composecounterapp.ui.theme.ComposeCounterAppTheme

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-type">ComponentActivity</span></span>() {
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(savedInstanceState: <span class="hljs-type">Bundle</span>?)</span></span> {
        <span class="hljs-keyword">super</span>.onCreate(savedInstanceState)
        setContent {
            ComposeCounterAppTheme {
                ComposeCounter()
            }
        }
    }
}

<span class="hljs-meta">@Composable</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ComposeCounter</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> primaryColor = Color(<span class="hljs-number">0xFF2196F3</span>)
    <span class="hljs-keyword">var</span> counter: <span class="hljs-built_in">Int</span> <span class="hljs-keyword">by</span> remember { mutableStateOf(<span class="hljs-number">0</span>) }


    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(text = <span class="hljs-string">"Compose Home Demo Page"</span>) },
                backgroundColor = primaryColor,
                contentColor = Color.White
            )
        },
        content = {
            Column(
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxHeight()
                    .fillMaxWidth()
            ) {
                Text(
                    text = <span class="hljs-string">"You have pushed this button this many times"</span>,
                )
                Text(
                    text = counter.toString(),
                    style = MaterialTheme.typography.h4,
                )
            }
        },
        floatingActionButton = {
            FloatingActionButton(
                onClick = {
                    counter += <span class="hljs-number">1</span>
                },
                backgroundColor = primaryColor,
                contentColor = Color.White
            ) {
                Icon(Icons.Filled.Add, contentDescription = <span class="hljs-string">"Add button"</span>)
            }
        },
    )
}
</code></pre>
<p>The complete project can be found on <a target="_blank" href="https://github.com/victoreronmosele/compose-counter-app">Github</a>.</p>
]]></content:encoded></item></channel></rss>