<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Santanu Paul</title>
    <description>The latest articles on Forem by Santanu Paul (@sntnupl).</description>
    <link>https://forem.com/sntnupl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F347734%2F2851404a-182e-4ccc-bd28-12d6f4d7379f.JPEG</url>
      <title>Forem: Santanu Paul</title>
      <link>https://forem.com/sntnupl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sntnupl"/>
    <language>en</language>
    <item>
      <title>Improving the local development setup for running MongoDB Replica Set with Docker</title>
      <dc:creator>Santanu Paul</dc:creator>
      <pubDate>Fri, 19 Mar 2021 19:36:21 +0000</pubDate>
      <link>https://forem.com/sntnupl/improving-the-local-development-setup-for-running-mongodb-replica-set-with-docker-2j7k</link>
      <guid>https://forem.com/sntnupl/improving-the-local-development-setup-for-running-mongodb-replica-set-with-docker-2j7k</guid>
      <description>&lt;p&gt;All associated source code used in this blog can be found at the following Github repository:  &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sntnupl" rel="noopener noreferrer"&gt;
        sntnupl
      &lt;/a&gt; / &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker" rel="noopener noreferrer"&gt;
        devcontainers-mongodb-replica-set-with-docker
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Docker based development container template to run a MongoDB replica set cluster in local machine.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;In my &lt;a href="https://dev.to/sntnupl/how-to-setup-a-mongodb-replica-set-for-development-using-docker-1de"&gt;previous post&lt;/a&gt;, we created a docker based development setup for running MongoDB Replica Set.  &lt;/p&gt;

&lt;p&gt;A flaw in that setup, was that although clients could connect to stand-alone nodes of the replica set, they could not connect to the replica set as a whole, as reported by &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker/issues/1" rel="noopener noreferrer"&gt;this issue&lt;/a&gt; on the &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker" rel="noopener noreferrer"&gt;github repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this blog post, we will see what can be done, to overcome this problem.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Root Cause
&lt;/h2&gt;

&lt;p&gt;As per the &lt;a href="https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#clients-use-the-hostnames-listed-in-the-replica-set-config-not-the-seed-list" rel="noopener noreferrer"&gt;MongoDB specifications&lt;/a&gt;:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the hostnames used in a replica set config are reachable from the client&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our previous blog, we initialized the following replica set configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongo1
&lt;span class="nv"&gt;MONGODB2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongo2
&lt;span class="nv"&gt;MONGODB3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongo3
:
var cfg &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"_id"&lt;/span&gt;: &lt;span class="s2"&gt;"rs0"&lt;/span&gt;,
    ...
    &lt;span class="s2"&gt;"members"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            ...
            &lt;span class="s2"&gt;"host"&lt;/span&gt;: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:27017"&lt;/span&gt;,
            ...
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            ...
            &lt;span class="s2"&gt;"host"&lt;/span&gt;: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:27017"&lt;/span&gt;,
            ...
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            ...
            &lt;span class="s2"&gt;"host"&lt;/span&gt;: &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB3&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:27017"&lt;/span&gt;,
            ...
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In essence, this would require the clients to able to reach the following hosts: &lt;code&gt;mongo1:27017, mongo2:27017, mongo3:27017&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;We can update &lt;code&gt;/etc/hosts&lt;/code&gt; file to point &lt;code&gt;127.0.0.1&lt;/code&gt; to &lt;code&gt;mongo1, mongo2, mongo3&lt;/code&gt; hostnames. But the portnumber &lt;code&gt;27017&lt;/code&gt; would still cause the problem - we can't have 3 nodes (services) running on the same port. &lt;/p&gt;

&lt;p&gt;Therefore, solution to the problem would be the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update &lt;code&gt;hosts&lt;/code&gt; file in client machines, to map hostnames &lt;code&gt;mongo1, mongo2, mongo3&lt;/code&gt; to &lt;code&gt;127.0.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;mongod&lt;/code&gt; service on the 3 nodes of the replica set. in different port numbers&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Implementing the 1st step is trivial. One would need to update &lt;code&gt;/etc/hosts&lt;/code&gt; file in Linux, or &lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt; in Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;127.0.0.1 localhost mongo1 mongo2 mongo3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go through the steps to implement the second step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changes to the docker compose file&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;mongo1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--port"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30001"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;30001&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30001:30001&lt;/span&gt; 
  &lt;span class="s"&gt;...&lt;/span&gt;

&lt;span class="na"&gt;mongo2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo2'&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo2'&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--port"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30002"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;30002&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30002:30002&lt;/span&gt; 
  &lt;span class="s"&gt;...&lt;/span&gt;

&lt;span class="na"&gt;mongo3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo3'&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo3'&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--port"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30003"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;30003&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30003:30003&lt;/span&gt; 
  &lt;span class="s"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will bring up &lt;code&gt;mongod&lt;/code&gt; services in the 3 nodes in 3 different ports.  &lt;/p&gt;

&lt;p&gt;Next, we modify the replica set configuration, by changing &lt;code&gt;mongosetup.sh&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mongo &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:30001 &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
var cfg = {
    "_id": "rs0",
    ...
    "members": [
        {
            ...
            "host": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:30001",
            ...
        },
        {
            ...
            "host": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:30002",
            ...
        },
        {
            ...
            "host": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB3&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:30003",
            ...
        }
    ]
};
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are essentially all the necessary changes needed to be done, to solve the problem.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;Once we make these changes, we could connect to the replica set using &lt;code&gt;mongo&lt;/code&gt; client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mongo &lt;span class="s2"&gt;"mongodb://localhost:30001,localhost:30002,localhost:30003/&amp;lt;MONGO_INITDB_DATABASE&amp;gt;?replicaSet=rs0"&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;MONGO_INITDB_USERNAME&amp;gt; &lt;span class="nt"&gt;--authenticationDatabase&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mongo &lt;span class="s2"&gt;"mongodb://mongo1:30001,mongo2:30002,mongo3:30003/&amp;lt;MONGO_INITDB_DATABASE&amp;gt;?replicaSet=rs0"&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;MONGO_INITDB_USERNAME&amp;gt; &lt;span class="nt"&gt;--authenticationDatabase&lt;/span&gt; admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fac7p7xzfmnf8phtdpzwi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fac7p7xzfmnf8phtdpzwi.jpg" alt="Connect to Replica set"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also view result of &lt;code&gt;db.isMaster()&lt;/code&gt; command to debug details like hosts of the replicaset a client must connect to, if the node is primary, etc.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqr02ieg661iyuxm2rfc0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqr02ieg661iyuxm2rfc0.jpg" alt="isMaster() output"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>mongodb</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Test Driven Development of Azure Functions with C# Part 3: Integration Tests</title>
      <dc:creator>Santanu Paul</dc:creator>
      <pubDate>Mon, 08 Mar 2021 07:13:38 +0000</pubDate>
      <link>https://forem.com/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116</link>
      <guid>https://forem.com/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-2-unit-tests-2ce8"&gt;previous post of this series&lt;/a&gt;, we saw how one can implement Unit Tests for an Azure Function.&lt;br&gt;
While Unit Tests forms the base of our Testing Pyramid, they are not enough from TDD completion point of view.&lt;br&gt;
The next phase would be to implement Integration Tests for our Azure Function.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Other Posts in this series&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl"&gt;Part 1 - Introduction to our sample Azure Functions application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-2-unit-tests-2ce8"&gt;Part 2 - Unit Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 3 - This Article &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have created this repository on Github, which contains all the associated source code used in this blog series:  &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sntnupl" rel="noopener noreferrer"&gt;
        sntnupl
      &lt;/a&gt; / &lt;a href="https://github.com/sntnupl/azure-functions-sample-unittest-integrationtest" rel="noopener noreferrer"&gt;
        azure-functions-sample-unittest-integrationtest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application to showcase how one can implement Unit and Integration testing for an Azure Functions application
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
The relevant Code for this post is under &lt;strong&gt;InvoiceProcessor.Tests.Integration&lt;/strong&gt; project in this repository.

&lt;p&gt;   &lt;/p&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwcw8tet8x6uuz1h5gbhj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwcw8tet8x6uuz1h5gbhj.jpg" alt="Testing Pyramid"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.ministryoftesting.com/dojo/lessons/the-mobile-test-pyramid" rel="noopener noreferrer"&gt;image source&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
Few of the characteristics that would separate these Integration tests from Unit Tests:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We would not mock the Event Triggers from Service Bus. We will make our Azure Function to get triggered by real messages coming from Service Bus Topic.&lt;/li&gt;
&lt;li&gt;We would not be mocking the &lt;code&gt;IBinder&lt;/code&gt; that our Azure function uses to read from Blob storage - we will use actual blob storage&lt;/li&gt;
&lt;li&gt;We would not be mocking the &lt;code&gt;IAsyncCollector&lt;/code&gt; to write to Azure Table Storage. We will be using real Table Storage that our Azure Function will write to, and we will be verifying those writes by reading from those real Table Storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can imagine, to achieve these feats, we would need to do some background work first.&lt;br&gt;
Before going into the details of those "background work", let us understand some concepts first.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;


&lt;h3&gt;
  
  
  Azure WebJobs SDK and how it is related to Azure Functions
&lt;/h3&gt;

&lt;p&gt;When we execute an Azure Functions app, it will start a console app. &lt;br&gt;
However, the actual "function" can't operate on its own and magically listen for event triggers - there is no magic. It needs a runtime container, &lt;strong&gt;a Host&lt;/strong&gt; - which has the capability of listening for event triggers and executing this Azure Function.&lt;br&gt;
This host will be responsible for starting the Azure Functions app, and managing its lifetime. It will also set up Dependency Injection, Logging and Configuration for the Azure Function app.&lt;br&gt;
This host is implemented via the &lt;code&gt;JobHost&lt;/code&gt; class in Azure WebJobs SDK (source code &lt;a href="https://github.com/Azure/azure-webjobs-sdk/blob/dev/src/Microsoft.Azure.WebJobs.Host/JobHost.cs" rel="noopener noreferrer"&gt;here&lt;/a&gt;).  Azure Functions is built on top of the &lt;a href="https://www.nuget.org/packages/Microsoft.Azure.WebJobs" rel="noopener noreferrer"&gt;Azure WebJobs SDK package&lt;/a&gt;, which allows it to use this &lt;code&gt;JobHost&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Of course, when we implement an Azure Function app, or even execute it, all of the details related to the &lt;code&gt;JobHost&lt;/code&gt; will be abstracted away from us. &lt;br&gt;
Inside an Azure Functions project, the only knobs you get to the Host is via the &lt;code&gt;host.json&lt;/code&gt; file. A complete reference to all the possible host settings that can be tweaked via host.json is available &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-host-json" rel="noopener noreferrer"&gt;at this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So once we understand this relationship, we can see that in order to setup our Integration Testing environment, we would need to &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bring up this host&lt;/li&gt;
&lt;li&gt;Tell it where to look for the Azure Functions to load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this knowledge under our belt, lets go through our Integration Test project.  &lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: All code discussed in the following sections is in the &lt;strong&gt;InvoiceProcessor.Tests.Integration&lt;/strong&gt; Project.  &lt;/p&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Designing the Integration Tests
&lt;/h3&gt;

&lt;p&gt;The SUT for our Integration Test is the Azure Function inside &lt;code&gt;InvoiceWorkitemEventHandler&lt;/code&gt; class. This function essentially does 3 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gets triggered as and when a new message arrives in a specific Azure Service Bus Topic, and parses this message to extract the location of an uploaded invoice&lt;/li&gt;
&lt;li&gt;Reads the blob from the above extracted location, and parses the content of the uploaded invoice file&lt;/li&gt;
&lt;li&gt;Finally it persists the content in a specific format inside Azure Table Storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As such, our Integration Tests will need to &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload a dummy invoice file into an Azure Blob Storage Container&lt;/li&gt;
&lt;li&gt;Create message with the location of the above created blob and send it to the Service Bus Topic our Azure Function is listening to&lt;/li&gt;
&lt;li&gt;Finally, wait for the corresponding entries to be created in the Azure Table Storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before doing these tasks, however, our Tests must ensure that any artifacts from previously run tests are cleaned off. In other words, it needs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Delete any Azure Blob Storage containers that previous tests would have created&lt;/li&gt;
&lt;li&gt;Create a new Azure Blob Storage container that will be used by the tests in the current run&lt;/li&gt;
&lt;li&gt;Delete any entries inside Azure Table Storage that was created by previously running tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entity that takes care of these tasks is the &lt;code&gt;EndToEndTestFixture&lt;/code&gt; class. &lt;br&gt;
We leverage the &lt;a href="https://xunit.net/docs/shared-context#class-fixture" rel="noopener noreferrer"&gt;Class Fixture&lt;/a&gt; pattern with this class, which allows us to create test context that will get shared across all tests in the &lt;code&gt;ValidInvoiceTests&lt;/code&gt; test suite. &lt;/p&gt;

&lt;p&gt;This class inherits from the &lt;code&gt;IAsyncLifetime&lt;/code&gt; interface of xUnit as well, which allows us to perform asynchronous setup tasks &lt;a href="https://github.com/xunit/xunit/blob/master/src/xunit.core/IAsyncLifetime.cs" rel="noopener noreferrer"&gt;during its initialization&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;


&lt;h3&gt;
  
  
  Writing our Integration Tests
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Arrange Part&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ValidInvoiceUploaded_GetsParsedAndSavedToTableStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;blobPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_testFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UploadSampleValidInvoice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before doing anything else, we are uploading our sample invoice file to Azure Blob Storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ValidInvoiceUploaded_GetsParsedAndSavedToTableStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;IHost&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HostBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureWebJobs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigureDefaultTestHost&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvoiceWorkitemEventHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;webjobsBuilder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;webjobsBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureStorage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;webjobsBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddServiceBus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;INameResolver&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// ..&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part where we set up a Host that will contain our Azure Functions. &lt;br&gt;
Specifically we have used the &lt;code&gt;ConfigureDefaultTestHost&amp;lt;InvoiceWorkitemEventHandler&amp;gt;()&lt;/code&gt; helper method to tell the host to look for Azure Functions inside &lt;code&gt;InvoiceWorkitemEventHandler&lt;/code&gt; class.&lt;br&gt;
We have also initialized the Azure Storage (&lt;code&gt;AddAzureStorage()&lt;/code&gt;) as well as the ServiceBus (&lt;code&gt;AddServiceBus()&lt;/code&gt;) extensions, as both of these are used by our SUT.&lt;br&gt;
once the host is created, we invoke &lt;code&gt;StartAsync()&lt;/code&gt; to start it.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Act Part&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ValidInvoiceUploaded_GetsParsedAndSavedToTableStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_testFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendInvoiceWorkitemEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we create a message with the location of the uploaded invoice file, and send it to the Azure Service Bus Topic.&lt;br&gt;
As soon as we push this message, our SUT should get triggered, and if everything goes well, Azure Table Storage would get updated with corresponding entries.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Assert Part&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ValidInvoiceUploaded_GetsParsedAndSavedToTableStorage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;

        &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AcmeOrderEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;parsedOrders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_testFixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForTableUpdateAndGetParsedOrders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Found &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parsedOrders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; invoices."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsedOrders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are waiting for the entries to be uploaded in the Table Storage, and asserting that 2 entries were created (our sample invoice contained two entries).&lt;/p&gt;

&lt;p&gt;Similar to running Unit Tests, we can use the Test Explorer in Visual Studio to execute this Integration Test as well.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;




&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this series of post, we saw how we can effectively perform Test Driven Development of Azure Functions.&lt;br&gt;
I have often seen developers worry about this aspect, when it comes to Azure Functions. &lt;br&gt;
The fact that these functions involves triggeres external entities like Azure Blob Storage, Azure Service Bus, etc, makes developers believe that unit/integration testing the Azure Functions will be a complex endeavour.&lt;br&gt;
As we have seen in this series of posts, that notion is not necessarily true.&lt;/p&gt;

&lt;p&gt;I hope that this series helped you to embrace TDD in your Azure functions app, and hopefully helped you become a better developer by shipping higher quality code. &lt;/p&gt;

&lt;p&gt;Thanks for reading! 🙂&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>azure</category>
      <category>dotnet</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Test Driven Development of Azure Functions with C# Part 2: Unit Tests</title>
      <dc:creator>Santanu Paul</dc:creator>
      <pubDate>Mon, 08 Mar 2021 07:08:05 +0000</pubDate>
      <link>https://forem.com/sntnupl/test-driven-development-of-azure-functions-with-c-part-2-unit-tests-2ce8</link>
      <guid>https://forem.com/sntnupl/test-driven-development-of-azure-functions-with-c-part-2-unit-tests-2ce8</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl"&gt;Part 1 of this series&lt;/a&gt; we started to explore how to implement Test Driven Development of Serverless Azure Functions application. &lt;br&gt;
We had an overview of some of the Event Bindings that allow Azure Functions to integrate with Azure Services like Azure Blob Storage, Azure Service Bus, etc. &lt;/p&gt;

&lt;p&gt;In this post, we will see how we can add Unit Tests to our Azure Functions application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other Posts in this series&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl"&gt;Part 1 - Introduction to our sample Azure Functions application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 2 - This Article &lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116"&gt;Part 3 - Integration Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have created this repository on Github, which contains all the associated source code used in this blog series:  &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sntnupl" rel="noopener noreferrer"&gt;
        sntnupl
      &lt;/a&gt; / &lt;a href="https://github.com/sntnupl/azure-functions-sample-unittest-integrationtest" rel="noopener noreferrer"&gt;
        azure-functions-sample-unittest-integrationtest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application to showcase how one can implement Unit and Integration testing for an Azure Functions application
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The relevant Code for this post is under &lt;strong&gt;InvoiceProcessor.Tests.Unit&lt;/strong&gt; project in this repository.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;




&lt;h3&gt;
  
  
  Adding Unit Test Project
&lt;/h3&gt;

&lt;p&gt;We will be using xUnit Test Framework to implement Unit Tests for our Azure Function.&lt;br&gt;&lt;br&gt;
You can visit &lt;a href="https://xunit.net/docs/getting-started/netcore/visual-studio" rel="noopener noreferrer"&gt;this official document&lt;/a&gt;, on how to get started with creating a new xUnit Test Project via Visual Studio.&lt;/p&gt;

&lt;p&gt;Refer to &lt;code&gt;InvoiceProcessor.Tests.Unit&lt;/code&gt; project within our &lt;a href="https://github.com/sntnupl/azure-functions-sample-unittest-integrationtest" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt;, for all the relevant code discussed in this post.&lt;br&gt;
File &lt;code&gt;InvoiceWorkitemEventHandlerShould.cs&lt;/code&gt; hosts all the tests that we will use to Unit Test our Azure Function.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;


&lt;h3&gt;
  
  
  Ground work
&lt;/h3&gt;

&lt;p&gt;Before writing the test cases, we would need to do create some resources that we are going to need for our testing.  Let's take a look at them.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Mocked Logger&lt;/strong&gt; &lt;br&gt;
We need to pass an instance of &lt;code&gt;ILogger&lt;/code&gt; to our Azure Function. &lt;br&gt;
Going forward, let's address this as SUT. &lt;/p&gt;

&lt;p&gt;To aid in our testing, we have created our own implementation of &lt;code&gt;ILogger&lt;/code&gt;.&lt;br&gt;
This implementation is available in &lt;code&gt;ListLogger.cs&lt;/code&gt; class within our project.&lt;/p&gt;

&lt;p&gt;Some implementations of Unit Testing uses &lt;code&gt;NullLoggerFactory&lt;/code&gt; to create an instance of &lt;code&gt;ILogger&lt;/code&gt; and pass the same in the Azure Function.&lt;br&gt;
That works for sure, but in our testing strategy, we intend to capture the logs generated by our SUT to validate sucess/failure of our test cases.&lt;br&gt;
As such we have created &lt;code&gt;ListLogger&lt;/code&gt;, which essentially stores all the published logs from SUT into a &lt;code&gt;List&amp;lt;string&amp;gt;&lt;/code&gt;. Within our test cases we will be checking the contents of this list to Assert certain behaviors.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Mocked AsyncCollector&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Our SUT takes &lt;code&gt;IAsyncCollector&amp;lt;AcmeOrderEntry&amp;gt;&lt;/code&gt; as its paramter. So we need to create a mock for this as well.&lt;br&gt;
To aid in our testing, we have created our own implementation of &lt;code&gt;IAsyncCollector&lt;/code&gt;, called &lt;code&gt;AsyncCollector&lt;/code&gt; - you can refer to &lt;code&gt;AsyncCollector.cs&lt;/code&gt; for the code.&lt;br&gt;
This class essentially wraps over a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;  as well. Any invocation to &lt;code&gt;AddAsync()&lt;/code&gt; will be adding items to this internal List.&lt;br&gt;
This again, allows us to verify that SUT invoked &lt;code&gt;AddAsync()&lt;/code&gt; properly on the &lt;code&gt;IAsyncCollector&amp;lt;AcmeOrderEntry&amp;gt;&lt;/code&gt; that got passed to it.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Other Mocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Apart from the above, we have mocked the following entities in our Test Project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IBinder&lt;/code&gt;: this is passed as the method parameter to SUT&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IAcmeInvoiceParser&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;This is used within the SUT code, to parse the blob text.&lt;/li&gt;
&lt;li&gt;To keep things simple, we have kept this as a publicly accessible static property within &lt;code&gt;InvoiceWorkitemEventHandler&lt;/code&gt; class, instead of using Dependency Injection.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these mocks is created within the constructor of our Test Suite, the &lt;code&gt;InvoiceWorkitemEventHandlerShould&lt;/code&gt; class.&lt;br&gt;
XUnit has few powerful strategies to allow developers share these resources as a &lt;em&gt;test context&lt;/em&gt;. I would suggest going through the official xUnit documentation on &lt;a href="https://xunit.net/docs/shared-context" rel="noopener noreferrer"&gt;Shared Context between Tests&lt;/a&gt;, for more details.&lt;br&gt;
To put things briefly, we wanted to recreate these mocks afresh for every test, hence we created them within the constructor of our Test Suite.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;


&lt;h3&gt;
  
  
  Writing our first Unit Test
&lt;/h3&gt;

&lt;p&gt;While writing these test cases, we must not loose sight of the fact that at the end of the day, an Azure Function is just a simple method which can be invoked as any normal function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ServiceBusTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workiteminvoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"InvoiceProcessor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ServiceBusReceiverConnection"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IBinder&lt;/span&gt; &lt;span class="n"&gt;blobBinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AcmeOrders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"StorageConnection"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;IAsyncCollector&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AcmeOrderEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tableOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So although we see the 1st parameter &lt;code&gt;string msg&lt;/code&gt; decorated with the &lt;code&gt;ServiceBusTrigger&lt;/code&gt; attribute, we dont need to do any extra work with respect to this attribute.&lt;br&gt;
To simulate a message coming this Azure Function via the Service Bus, we just pass a string in the 1st parameter, thats it!&lt;/p&gt;

&lt;p&gt;With that being said, lets go through one of the test cases which validates that an invalid message arriving via Service Bus would get rejected by our SUT.&lt;br&gt;
This test is housed with &lt;code&gt;RejectInvalidMessagesFromServiceBus()&lt;/code&gt; method of &lt;code&gt;InvoiceWorkitemEventHandlerShould&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Arrange Part&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RejectInvalidMessagesFromServiceBus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_mockParser&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAny&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;_testLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_mockParsedOrders&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TryParseCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AcmeOrder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AcmeOrder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are setting up our mocked &lt;code&gt;IAcmeInvoiceParser&lt;/code&gt;. We have used the wonderful &lt;a href="https://github.com/moq/moq4" rel="noopener noreferrer"&gt;moq package&lt;/a&gt; to create these mocks.&lt;/p&gt;

&lt;p&gt;This code essentially states that whenever SUT invokes &lt;code&gt;TryParse()&lt;/code&gt; method on our mock, return a value of &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We dont really care about the &lt;code&gt;Stream&lt;/code&gt; parameter that the SUT passes to &lt;code&gt;TryParse()&lt;/code&gt; - &lt;code&gt;It.IsAny&amp;lt;Stream&amp;gt;()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;But we do expect the SUT to pass our very own &lt;code&gt;ILogger&lt;/code&gt; implementation (&lt;code&gt;ListLogger&lt;/code&gt;) to be passed in &lt;code&gt;TryParse()&lt;/code&gt; - &lt;code&gt;_testLogger&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Callback()&lt;/code&gt; part is to essentially mock-populate the &lt;code&gt;out&lt;/code&gt; parameter that SUT will be passing to &lt;code&gt;TryParse()&lt;/code&gt; method.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RejectInvalidMessagesFromServiceBus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="n"&gt;_mockBlobBinder&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAny&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlobAttribute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sut&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvoiceWorkitemEventHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;InvoiceWorkitemEventHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceParser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_mockParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are setting up our mocked &lt;code&gt;IBinder&lt;/code&gt; instance.&lt;br&gt;
SUT can pass any &lt;code&gt;BlobAttribute&lt;/code&gt;, and &lt;code&gt;default&lt;/code&gt; value of &lt;code&gt;CancellationToken&lt;/code&gt; (which is null), and our mock will return a &lt;code&gt;Task&amp;lt;stream&amp;gt;&lt;/code&gt; with value &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We have also created an instance of &lt;code&gt;InvoiceWorkitemEventHandler&lt;/code&gt; class and injected our mocked &lt;code&gt;IAcmeInvoiceParser&lt;/code&gt; to its public static property.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Act Part&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RejectInvalidMessagesFromServiceBus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

   &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;InvoiceWorkitemEventHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Invalid work item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_mockBlobBinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_mockCollector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;_testLogger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We invoke the SUT, with an invalid message &lt;code&gt;"Invalid work item"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
&lt;strong&gt;Assert Part&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RejectInvalidMessagesFromServiceBus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_testLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;NotBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;NotBeEmpty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;NotContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empty Invoice Workitem."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid Invoice Workitem."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;_mockBlobBinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAny&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlobAttribute&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;Times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Never&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we mentioned earlier, we will be leveraging our &lt;code&gt;ListLogger&lt;/code&gt; class to verify SUT behavior.&lt;/p&gt;

&lt;p&gt;Inside the SUT test code, we first check if the message from Service Bus is empty. If it is, the SUT logs message &lt;code&gt;Empty Invoice Workitem.&lt;/code&gt; and exit.&lt;br&gt;
Then it checks if the message from Service Bus is invalid. If it is it will log message &lt;code&gt;Invalid Invoice Workitem.&lt;/code&gt; and exit.&lt;/p&gt;

&lt;p&gt;What we have asserted here is that the first message is not logged by SUT, as message was not empty: &lt;code&gt;logs.Should().NotContain(l =&amp;gt; l.Contains("Empty Invoice Workitem."));&lt;/code&gt;&lt;br&gt;
However the second message should get logged, becuase the message was an invalid &lt;code&gt;InvoiceWorkitemMessage&lt;/code&gt;: &lt;code&gt;logs.Should().Contain(l =&amp;gt; l.Contains("Invalid Invoice Workitem."));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lastly, we also assert that SUT must not invoke &lt;code&gt;BindAsync&lt;/code&gt; on the &lt;code&gt;IBinder&lt;/code&gt; passed to it, because the method should have exited.&lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;




&lt;p&gt;Here, we have leveraged the &lt;a href="https://github.com/fluentassertions/fluentassertions" rel="noopener noreferrer"&gt;FluentAssertions package&lt;/a&gt; to write the descriptive Assertions messages.&lt;/p&gt;

&lt;p&gt;To execute test cases like these, use the Test Explorer in Visual Studio.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1ow0t1v9lf2kn7o9ttx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1ow0t1v9lf2kn7o9ttx.jpg" alt="Visual Studio Test Explorer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116"&gt;last post of this series&lt;/a&gt;, we will see how we can perform Integration Testing on our Azure Function.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>azure</category>
      <category>dotnet</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Test Driven Development of Azure Functions with C# Part 1: Introduction to the application</title>
      <dc:creator>Santanu Paul</dc:creator>
      <pubDate>Mon, 08 Mar 2021 07:04:14 +0000</pubDate>
      <link>https://forem.com/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl</link>
      <guid>https://forem.com/sntnupl/test-driven-development-of-azure-functions-with-c-part-1-introduction-to-the-application-1pcl</guid>
      <description>&lt;p&gt;Over the last few years, Serverless computing has become one of the most appealing choice for developing backend application.&lt;br&gt;&lt;br&gt;
With PaaS offerings like Azure App Service, we already had the benefit of not having to manage and maintain our Server infrastructure.&lt;br&gt;&lt;br&gt;
The cloud service provider will do all the heavy lifting required, to manage the infrastructure, perform auto scaling and auto provisioning of the application.  &lt;/p&gt;

&lt;p&gt;Now with FaaS offerings like Azure Functions, AWS Lambda, backend developers have been given the additional benefit of not having to maintain all the application server code.&lt;br&gt;&lt;br&gt;
This is a very powerful abstraction, which makes perfect sense for event-driven applications. Once we write the piece of code that you want to execute for certain event(s), Azure Functions will do the hard work of  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hosting this code (in some abstracted server infrastructure)&lt;/li&gt;
&lt;li&gt;provide the glue between the actual event and your application (ensure that your code actually gets triggered as and when the actual event gets triggered)&lt;/li&gt;
&lt;li&gt;auto-scale the application based on the volume of events received&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For event driven applications that runs on the cloud, this is a very powerful model indeed.  &lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
These days, we are seeing more and more organizations curating their backend systems running on the Cloud as a Microservice based architecture.&lt;br&gt;&lt;br&gt;
In Azure for example, these microservices will leverage cloud services like Blob Storage, Message Queues, Service Bus, Event Hubs, Table Storage etc to communicate with each other.  &lt;/p&gt;

&lt;p&gt;Azure Functions allows us to register our code to get notified for activities happening on these services, by registering to get notified for specific "events".&lt;br&gt;&lt;br&gt;
For example, an Azure Function can get notified for a "Blob Event", that indicates that a blob was created at a certain path. Similarly, a message arriving on a Service Bus Topic can notify an Azure Function who has registered for it.&lt;br&gt;&lt;br&gt;
The icing on the cake is that registration for a certain event is simply via adding event bindings of these services in the methos parameters - as simple as it gets!  &lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
In this post, I would like to share a practical guide towards Test Driven Development (TDD) of Serverless Applications using Azure Functions.&lt;br&gt;&lt;br&gt;
When I started developing serverless applications using Azure Functions, writing Unit Tests and Integration tests were two aspects in which I could not find good, detailed guides.&lt;br&gt;&lt;br&gt;
Over the last few months, I do see ample resources guiding developers on how to Unit Test their Azure Functions app, but there is not much guidance on how to implement Integration Tests in these apps.&lt;br&gt;&lt;br&gt;
In this series of posts, I would try to share my learnings, which hopefully would help future developers in their journey.  &lt;/p&gt;

&lt;p&gt;This post will be the first of a three-part blog series, in which I shall introduce a sample Azure Functions application written in C#.  &lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Other Posts in this series&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Part 1 - This Article&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-2-unit-tests-2ce8"&gt;Part 2 - Unit Tests&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-3-integration-tests-5116"&gt;Part 3 - Integration Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, I have created this repository on Github, which contains all the associated source code used in this blog series:  &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sntnupl" rel="noopener noreferrer"&gt;
        sntnupl
      &lt;/a&gt; / &lt;a href="https://github.com/sntnupl/azure-functions-sample-unittest-integrationtest" rel="noopener noreferrer"&gt;
        azure-functions-sample-unittest-integrationtest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample application to showcase how one can implement Unit and Integration testing for an Azure Functions application
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;   &lt;/p&gt;




&lt;h3&gt;
  
  
  The Sample Application
&lt;/h3&gt;

&lt;p&gt;Our sample application is an Azure Function, which will get triggered by an event triggered by Azure Service Bus, when a message is sent to Service Bus Topic.&lt;br&gt;
This message essentially contains location to a blob in  Azure Blob Storage. &lt;br&gt;
Once our app is able to read the text contents of this blob, it will try to parse its contents as (fictious) Invoice entries of Acme Corp. &lt;br&gt;
These parsed Acme Corp invoice entries will then be persisted in Azure Table Storage. &lt;/p&gt;

&lt;p&gt;If you have worked with Azure functions a question you might be wondering, is that instead of using Service Bus trigger, why don't we use the trigger generated by the Blob storage itself.&lt;br&gt;
That can be a valid solution, in certain use cases. I chose to architect our application such that we read the location from a service bus message because of the following reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I can send additional metadata about the upload blob in a cleaner fashion. For e.g., I can pass metadata like &lt;code&gt;transaction-id&lt;/code&gt;, &lt;code&gt;user-id&lt;/code&gt; in the Service Bus message&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I can choose to be flexible on the actual location of the uploaded file (what if some client wants to use AWS S3?)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another application who wants these uploaded invoices to be processed, can choose to delay the processing, or may even want to batch them up. Both of these can be easily achieved if we choose Service Bus Trigger. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
Project &lt;strong&gt;InvoiceProcessor&lt;/strong&gt; hosts our Azure Function. inside &lt;code&gt;InvoiceWorkitemEventHandler.cs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"InvoiceWorkitemEventHandler"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ServiceBusTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workiteminvoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"InvoiceProcessor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ServiceBusReceiverConnection"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IBinder&lt;/span&gt; &lt;span class="n"&gt;blobBinder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AcmeOrders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"StorageConnection"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;IAsyncCollector&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AcmeOrderEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tableOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;[ServiceBusTrigger("workiteminvoice", "InvoiceProcessor", Connection = "ServiceBusReceiverConnection")] string msg,&lt;/code&gt; shows just how easy it is, to hook to event triggers generated from Azure Service Bus.  &lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
All we need is to use the &lt;code&gt;ServiceBusTrigger&lt;/code&gt; attribute to decorate the 1st method parameter.&lt;br&gt;&lt;br&gt;
It takes 3 arguments itself: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Topic Name&lt;/em&gt;, &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Subscription Name&lt;/em&gt;, and &lt;/li&gt;
&lt;li&gt;the name of the Configuration property, that stores the ServiceBus &lt;em&gt;Connection&lt;/em&gt; string.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want more details on this attribute, I would suggest you to go through &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=csharp" rel="noopener noreferrer"&gt;this article&lt;/a&gt; - which explains the Azure Service Trigger in more details.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
The next method parameter to our Azure Function is an &lt;code&gt;IBinder&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
We will be using this to perform input-binding (i.e., bind to perform read operations) to a particular blob in Azure Blob Storage.  &lt;/p&gt;

&lt;p&gt;&lt;code&gt;ServiceBusTrigger&lt;/code&gt; allowed us to imperatively perform input-binding to Service Bus messages. This works because we know that we will always be looking for messages in &lt;code&gt;workiteminvoice&lt;/code&gt; topic, using &lt;code&gt;InvoiceProcessor&lt;/code&gt; subscription.&lt;br&gt;&lt;br&gt;
We can't use the corresponding &lt;code&gt;Blob&lt;/code&gt; Trigger - because we will know about the blob location at runtime, ONLY after we parse the message received from Service Bus.&lt;/p&gt;

&lt;p&gt;For scenarios like these, when we need run time binding we can use the &lt;code&gt;IBinder&lt;/code&gt; input parameter.&lt;br&gt;&lt;br&gt;
We will soon see, that once we know the blob location, how we can use &lt;code&gt;BindAsync&lt;/code&gt; method on the &lt;code&gt;IBinder&lt;/code&gt; instance to perform input binding to the blob.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
The last parameter, allows us to perform output binding to Azure Table Storage: &lt;code&gt;[Table("AcmeOrders", Connection = "StorageConnection")] IAsyncCollector&amp;lt;AcmeOrderEntry&amp;gt; tableOutput&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Imperative binding works here, because we know we will always be writing to the table named &lt;code&gt;AcmeOrders&lt;/code&gt;, withing the Azure Storage account whose connection string is specified in the configuration property &lt;code&gt;StorageConnection&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Also, since we might be writing (output-ing) multiple entries to this table, we need to use &lt;code&gt;IAsyncCollector&amp;lt;T&amp;gt;&lt;/code&gt; as the concrete type. Think of these as &lt;code&gt;out&lt;/code&gt; parameters to an async Azure Function.&lt;br&gt;&lt;br&gt;
We will be invoking &lt;code&gt;AddAsync()&lt;/code&gt; method on this to write multiple entries to the Azure Table Storage.  &lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;


&lt;h3&gt;
  
  
  The Code
&lt;/h3&gt;

&lt;p&gt;Let us now take a look into the first part of this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;InvoiceWorkitemMessage&lt;/span&gt; &lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"C# ServiceBus topic trigger function processed message: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empty Invoice Workitem."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvoiceWorkitemMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonSerializationOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JsonException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid Invoice Workitem."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Unexpected Error."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLocationType&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;DataLocationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AzureBlob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Unsupported data location type &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLocationType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLocation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Empty data location."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing too fancy here. We are essentially trying to parse the message received from Service Bus as an &lt;code&gt;InvoiceWorkitemMessage&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
If the message is valid, and we are able to parse it, we check that the blob location is of type Azure Blob Storage (currently we support ONLY Azure Blob Storage).  &lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
Once we know the blob path, we can use the &lt;code&gt;IBinder&lt;/code&gt; instance to bind to the specific blob path. As discussed earlier, we will be using the &lt;code&gt;BindAsync&lt;/code&gt; method to get a &lt;code&gt;Stream&lt;/code&gt; of the blob contents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blobAttr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlobAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataLocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileAccess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"StorageConnection"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blobStream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blobBinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;blobAttr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;br&gt;&lt;br&gt;
We will then parse the contents of this blob, using &lt;code&gt;InvoiceParser&lt;/code&gt; class instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;InvoiceParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AcmeOrder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to parse invoice."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will skip the inner behavior of the &lt;code&gt;TryParse&lt;/code&gt; method, as it doesn't really add to our scope of discussion here. &lt;br&gt;
You can look into the &lt;code&gt;InvoiceParser.cs&lt;/code&gt; file for more details.&lt;/p&gt;

&lt;p&gt; &lt;br&gt;&lt;br&gt;
If everything goes fine, and we are able to parse the contents of the blob as one or more &lt;code&gt;AcmeOrder&lt;/code&gt;s, we can persist them in Azure Table Storage.&lt;br&gt;
As discussed earlier, we use &lt;code&gt;AddAsync()&lt;/code&gt; method on the &lt;code&gt;IAsyncCollector&amp;lt;AcmeOrderEntry&amp;gt;&lt;/code&gt; to achieve this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tableOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AcmeOrderEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceWorkItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Added table record for BigBasket Order number: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;br&gt;&lt;br&gt;
To perform TDD with this sample application, we need to write Unit Tests and Integration Tests against this code. &lt;/p&gt;

&lt;p&gt;   &lt;/p&gt;




&lt;p&gt;In the &lt;a href="https://dev.to/sntnupl/test-driven-development-of-azure-functions-with-c-part-2-unit-tests-2ce8"&gt;next post&lt;/a&gt;, we will discuss how we can implement Unit Tests for this Azure Function. See you there!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>azure</category>
      <category>serverless</category>
    </item>
    <item>
      <title>How to setup a MongoDB Replica set for development using Docker</title>
      <dc:creator>Santanu Paul</dc:creator>
      <pubDate>Mon, 15 Feb 2021 15:41:19 +0000</pubDate>
      <link>https://forem.com/sntnupl/how-to-setup-a-mongodb-replica-set-for-development-using-docker-1de</link>
      <guid>https://forem.com/sntnupl/how-to-setup-a-mongodb-replica-set-for-development-using-docker-1de</guid>
      <description>&lt;p&gt;In this post I will be sharing how I configure and run a MongoDB &lt;a href="https://docs.mongodb.com/manual/replication/" rel="noopener noreferrer"&gt;replica set&lt;/a&gt; on my local machine, using Docker.&lt;br&gt;&lt;br&gt;
While there are ample examples on the internet about running MongoDB containers, not many of them focus on how to bring up a MongoDB replica set.  &lt;/p&gt;

&lt;p&gt;Doing development against single node MongoDB is fine for starting, but as one starts using features like &lt;a href="https://docs.mongodb.com/manual/core/transactions" rel="noopener noreferrer"&gt;transactions&lt;/a&gt;, it becomes imperative that the MongoDB deployment has replica set enabled (or sharded cluster, a topic for future discussion).&lt;br&gt;&lt;br&gt;
Creating and developing against a MongoDB replica set in local development setup helps you learn the topic, and be more confident that your application code is already adapted to run against replica sets in production.  &lt;/p&gt;

&lt;p&gt;To follow along, you will need to have &lt;strong&gt;Docker&lt;/strong&gt;, and &lt;strong&gt;Docker Compose&lt;/strong&gt; installed in your machine.&lt;br&gt;&lt;br&gt;
To install Docker, I suggest following the official documentation &lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;at this link&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Once Docker is installed, install Docker Compose by following the links from &lt;a href="https://docs.docker.com/compose/install/" rel="noopener noreferrer"&gt;this link&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;I have created &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker" rel="noopener noreferrer"&gt;this Github repository&lt;/a&gt;, which contains all the required artifacts to follow along with this tutorial.  &lt;/p&gt;
&lt;h2&gt;
  
  
  The Docker Compose file
&lt;/h2&gt;

&lt;p&gt;Once Docker and Docker Compose are both installed on your machine, we can start writing the Docker Compose file, that contains directives to bring up the MongoDB replica set. I will show the full &lt;code&gt;docker-compose.yml&lt;/code&gt; file first, and then explain the individual details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;  &lt;span class="c1"&gt;# Docker Engine release 19.03.0+ [https://docs.docker.com/compose/compose-file/]&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 

    &lt;span class="c1"&gt;# setup MongoDB cluster for production&lt;/span&gt;
    &lt;span class="na"&gt;mongo-replica-setup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo-setup&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
        &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
        &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/scripts/mongosetup.sh:/scripts/mongosetup.sh&lt;/span&gt;
        &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/scripts/mongosetup.sh"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
        &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo1&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo2&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo3&lt;/span&gt;

    &lt;span class="na"&gt;mongo1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
        &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;on-failure'&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;27017&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30001:27017&lt;/span&gt; 
        &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dataMongo1:/data/db&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;logMongo1:/var/log/mongodb&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/initdb.d/:/docker-entrypoint-initdb.d/&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/mongod.conf:/etc/mongod.conf&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/file.key:/auth/file.key&lt;/span&gt;
        &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test $$(echo "rs.status().ok" | mongo -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --quiet) -eq &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
            &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
            &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
        &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_DATABASE}&lt;/span&gt;

    &lt;span class="na"&gt;mongo2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo2'&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo2'&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;on-failure'&lt;/span&gt;
        &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;27017&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30002:27017&lt;/span&gt;  
        &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dataMongo2:/data/db&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;logMongo2:/var/log/mongodb&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/mongod.conf:/etc/mongod.conf&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/file.key:/auth/file.key&lt;/span&gt;
        &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_DATABASE}&lt;/span&gt;
        &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo1&lt;/span&gt;

    &lt;span class="na"&gt;mongo3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo3'&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo3'&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;on-failure'&lt;/span&gt;
        &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;27017&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30003:27017&lt;/span&gt;  
        &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dataMongo3:/data/db&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;logMongo3:/var/log/mongodb&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/mongod.conf:/etc/mongod.conf&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/file.key:/auth/file.key&lt;/span&gt;
        &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
            &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_DATABASE}&lt;/span&gt;
        &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo1&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;dataMongo1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dataMongo2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dataMongo3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logMongo1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logMongo2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;logMongo3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;netApplication&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwibnhlmmmfqi7gfd0atf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwibnhlmmmfqi7gfd0atf.jpg" alt="Replica set nodes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we are trying to setup a MongoDB replica set with 3 nodes.&lt;br&gt;&lt;br&gt;
One of these nodes will be the &lt;strong&gt;Primary&lt;/strong&gt; node - all writes to the database will happen via this node.&lt;br&gt;&lt;br&gt;
The remaining two, are &lt;strong&gt;Secondary&lt;/strong&gt; nodes, providing two-levels of data replication in the setup.&lt;br&gt;&lt;br&gt;
A solid explanation of how data replication works in MongoDB can be found &lt;a href="https://docs.mongodb.com/manual/replication/#replication-in-mongodb" rel="noopener noreferrer"&gt;at this link&lt;/a&gt;.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Exploring the docker-compose file
&lt;/h2&gt;

&lt;p&gt;Coming back to the &lt;code&gt;yaml&lt;/code&gt; file, let's go through the service declaration of &lt;code&gt;mongo1&lt;/code&gt; and see what we are trying to achieve.&lt;/p&gt;
&lt;h3&gt;
  
  
  The basic stuff
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mongo1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;on-failure'&lt;/span&gt;
    &lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;27017&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30001:27017&lt;/span&gt; 
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Nothing fancy here, we are targetting MongoDB server version 4.2 to run these containers.&lt;br&gt;&lt;br&gt;
We have declared hostname and container name, and set the container &lt;a href="https://docs.docker.com/config/containers/start-containers-automatically/" rel="noopener noreferrer"&gt;restart policy&lt;/a&gt; to &lt;code&gt;on-failure&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
On the ports front, we have exposed &lt;code&gt;27017&lt;/code&gt; to other docker containers running on the same docker network. Also, this port is mapped to host port &lt;code&gt;30001&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Lastly, we will be running our replica set on a named network.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Starting the mongod instance
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will start the &lt;code&gt;mongod&lt;/code&gt; service with the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-f /etc/mongod.conf&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;specifies that the runtime configuration options should be picked up from the provided &lt;code&gt;mongod.conf&lt;/code&gt; file.
&lt;/li&gt;
&lt;li&gt;we will see the contents of this file later&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-keyFile /auth/file.key&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;In our replica set, the nodes will use the contents of a shared keyfile to authenticate to each other.
&lt;/li&gt;
&lt;li&gt;Here we are specifying the path to the keyfile&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--replSet ${MONGO_REPLICA_SET_NAME}&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;This configures the node to run in a replica set.
&lt;/li&gt;
&lt;li&gt;We are also specifying the name of the replica set, which is to be picked up from the environment variable &lt;code&gt;${MONGO_REPLICA_SET_NAME}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--bind_ip_all&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;This specifies the &lt;code&gt;mongod&lt;/code&gt; instance to bind to all IPv4 addresses (0.0.0.0).
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Container volumes
&lt;/h3&gt;

&lt;p&gt;Before proceeding it is worth mentioning again, that I have created &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker" rel="noopener noreferrer"&gt;this Github repository&lt;/a&gt; to house all the artifacts mentioned here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongoData1:/data/db&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongoLog1:/var/log/mongodb&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/initdb.d/:/docker-entrypoint-initdb.d/&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/mongod.conf:/etc/mongod.conf&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/file.key:/auth/file.key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have setup a few &lt;a href="https://docs.docker.com/storage/volumes/" rel="noopener noreferrer"&gt;Docker managed volumes&lt;/a&gt; to store the container's data in host's file system.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mongoData1&lt;/code&gt;: This will act as the peristent store for MongoDB's data.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mongoLog1&lt;/code&gt;: All of MongoDB's logs for this container will be stored here.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./.docker/mongodb/initdb.d/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;When &lt;code&gt;mongod&lt;/code&gt; container starts for the &lt;em&gt;very first time&lt;/em&gt;, it will look for a directory named &lt;code&gt;/docker-entrypoint-initdb.d&lt;/code&gt;, and execute all files with extensions &lt;code&gt;.sh&lt;/code&gt; and &lt;code&gt;.js&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;We map this directory to &lt;code&gt;./.docker/mongodb/initdb.d/&lt;/code&gt; in host. &lt;/li&gt;
&lt;li&gt;As we will see later, we will use this directory to store a bash script that allows us to create a user.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;./.docker/mongodb/mongod.conf&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;We are essentially providing the path (in host) from where MongoDB will pick up its config file.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;./.docker/mongodb/file.key&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Like above volume, here we are specifying the keyfile that this MongoDB node will be using to authenticate itself to other nodes in the replica set.
&lt;/li&gt;
&lt;li&gt;Steps to create this keyfile is provided in &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker" rel="noopener noreferrer"&gt;the Github repository&lt;/a&gt; that I have created.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
    &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_DATABASE}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have specified that the docker compose will be using the &lt;code&gt;.env&lt;/code&gt; file to read environment variables and inject them into the container.&lt;br&gt;&lt;br&gt;
This allows us to specify that the database's root username/password and initial database name to be picked up from specific environment variables.&lt;br&gt;&lt;br&gt;
In my &lt;a href="https://github.com/sntnupl/devcontainers-mongodb-replica-set-with-docker" rel="noopener noreferrer"&gt;github repository&lt;/a&gt;, I have provided a sample env file that you can use to create your own &lt;code&gt;.env&lt;/code&gt; file.&lt;br&gt;&lt;br&gt;
Contents of the sample env file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# MongoDB
MONGO_URL=mongodb://mongodb:27017
MONGO_INITDB_ROOT_USERNAME=&amp;lt;root_username&amp;gt;
MONGO_INITDB_ROOT_PASSWORD=&amp;lt;root_password&amp;gt;
MONGO_INITDB_DATABASE=&amp;lt;app_db-name&amp;gt;
MONGO_INITDB_USERNAME=&amp;lt;app_username&amp;gt;
MONGO_INITDB_PASSWORD=&amp;lt;app_password&amp;gt;
MONGO_REPLICA_SET_NAME=rs0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One must make sure not to commit this file in github, as these contains secrests like root username/password.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F219a3dpztqttnvlhsakg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F219a3dpztqttnvlhsakg.jpg" alt="Tree view of files and directories"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image provides a tree view of the files and directories discussed here.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Health Check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test $$(echo "rs.status().ok" | mongo -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --quiet) -eq &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are specifying the healthcheck command that docker will use to check if the container is up and running.  &lt;/p&gt;

&lt;p&gt;To summarize, the &lt;code&gt;mongo1&lt;/code&gt; service's specification looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mongo1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo1'&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;on-failure'&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/mongod.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--keyFile"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/auth/file.key"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--replSet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${MONGO_REPLICA_SET_NAME}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--bind_ip_all"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;27017&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;30001:27017&lt;/span&gt; 
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongoData1:/data/db&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongoLog1:/var/log/mongodb&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/initdb.d/:/docker-entrypoint-initdb.d/&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/mongod.conf:/etc/mongod.conf&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/file.key:/auth/file.key&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test $$(echo "rs.status().ok" | mongo -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --quiet) -eq &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
        &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
        &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
        &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_DATABASE}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other two nodes - &lt;code&gt;mongo1&lt;/code&gt; and &lt;code&gt;mongo2&lt;/code&gt; is a subset of this specification. The only things missing are the healthcheck section, and a volume that maps to &lt;code&gt;docker-entrypoint-initdb.d&lt;/code&gt;. This is because we have designed this setup such that &lt;code&gt;mongo1&lt;/code&gt; will become the primary and the rest secondary.&lt;br&gt;&lt;br&gt;
Being secondary nodes, they dont need the &lt;code&gt;initdb&lt;/code&gt; part to work.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Create the Mongo Cluster
&lt;/h2&gt;

&lt;p&gt;Once all the nodes in the replica set is up, we will need to initiate Replica set configuration on these nodes.&lt;br&gt;&lt;br&gt;
We will be using a 4th container - &lt;code&gt;mongo-replica-setup&lt;/code&gt; to do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mongo-replica-setup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo-setup&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mongo:4.2'&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on-failure&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;netApplication&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.docker/mongodb/scripts/mongosetup.sh:/scripts/mongosetup.sh&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/scripts/mongosetup.sh"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_USERNAME}&lt;/span&gt;
        &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_INITDB_ROOT_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo1&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo2&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this container starts, it will connect to &lt;code&gt;mongo1&lt;/code&gt;, and execute a script that will initiate a replica set over &lt;code&gt;mongo1&lt;/code&gt;, &lt;code&gt;mongo2&lt;/code&gt; and &lt;code&gt;mongo3&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Once the script is executed this container exits.  &lt;/p&gt;

&lt;p&gt;Its important to note that we have set the &lt;code&gt;depends_on&lt;/code&gt; field of this container to &lt;code&gt;mongo1&lt;/code&gt;, &lt;code&gt;mongo2&lt;/code&gt; and &lt;code&gt;mongo3&lt;/code&gt;, which ensures that this container will start only after all those 3 containers are up. This is important, as you wont want to run the script without all the nodes of the replica set being up.  &lt;/p&gt;

&lt;p&gt;The following is the script that this container runs, to initiate the replica set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongo1
&lt;span class="nv"&gt;MONGODB2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongo2
&lt;span class="nv"&gt;MONGODB3&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongo3

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"**********************************************"&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Waiting for startup.."&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;30
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"done"&lt;/span&gt;

&lt;span class="nb"&gt;echo &lt;/span&gt;SETUP.sh &lt;span class="nb"&gt;time &lt;/span&gt;now: &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s2"&gt;"%T"&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;
mongo &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:27017 &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
var cfg = {
    "_id": "rs0",
    "protocolVersion": 1,
    "version": 1,
    "members": [
        {
            "_id": 0,
            "host": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:27017",
            "priority": 2
        },
        {
            "_id": 1,
            "host": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:27017",
            "priority": 0
        },
        {
            "_id": 2,
            "host": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MONGODB3&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:27017",
            "priority": 0,
        }
    ]
};
rs.initiate(cfg, { force: true });
rs.secondaryOk();
db.getMongo().setReadPref('primary');
rs.status();
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you look closely, we have set the &lt;code&gt;priority&lt;/code&gt; for &lt;code&gt;mongo1&lt;/code&gt; higher than the other two nodes, which ensures that initially, &lt;code&gt;mongo1&lt;/code&gt; will be acting as the Primary node.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;To start the replica set, run &lt;code&gt;docker-compose up -d&lt;/code&gt; from command line.&lt;br&gt;&lt;br&gt;
Once everything comes up, you can run &lt;code&gt;docker-compose status&lt;/code&gt;, and see something like this:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwn7vr5y9k1ftxqzjlwez.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwn7vr5y9k1ftxqzjlwez.jpg" alt="Replica set status"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To connect to the replica set, you can use mongo client like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mongo &lt;span class="s2"&gt;"mongodb://localhost:30001,localhost:30002,localhost:30003/&amp;lt;MONGO_INITDB_DATABASE&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;MONGO_INITDB_USERNAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fj7sca0rmvukrxpz4so9f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fj7sca0rmvukrxpz4so9f.jpg" alt="Connect to Replica set"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>mongodb</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
