<?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>DEV Community: Jean-Paul Delimat</title>
    <description>The latest articles on DEV Community by Jean-Paul Delimat (@jpdelimat).</description>
    <link>https://dev.to/jpdelimat</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%2F153211%2Fd5b13b74-7b6c-4355-a5bf-568eb7f00f99.jpg</url>
      <title>DEV Community: Jean-Paul Delimat</title>
      <link>https://dev.to/jpdelimat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jpdelimat"/>
    <language>en</language>
    <item>
      <title>Continuous Integration without unit tests</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Thu, 06 Feb 2020 06:54:20 +0000</pubDate>
      <link>https://dev.to/jpdelimat/continuous-integration-without-unit-tests-4i2j</link>
      <guid>https://dev.to/jpdelimat/continuous-integration-without-unit-tests-4i2j</guid>
      <description>&lt;p&gt;Do you think continuous integration is not for you because you have no automated tests? Or no unit tests at all? Not true. Tests are important. But there are many more aspects to continuous integration than just testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Building the codebase
&lt;/h2&gt;

&lt;p&gt;It is the most critical issue continuous integration should solve though. The main branch of your codebase should always build/compile.  It seems silly to even mention it.  &lt;/p&gt;

&lt;p&gt;Take a team of 12. Say 1 faulty commit gets into the main branch. Everybody pulls. Here starts the process of finding what's wrong and coordinating who should or will fix it. The confusion puts your whole team out of focus for around 30 minutes. Plus the frustration it brings.&lt;/p&gt;

&lt;p&gt;Let's say this happen once a week (everybody makes mistakes eventually). 30 minutes x 12 people is 8 lost hours per week.&lt;/p&gt;

&lt;p&gt;If you are OK with that you might as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  set up a CI process preventing faulty builds to get into the main branch&lt;/li&gt;
&lt;li&gt;  give away a day off to one developer every week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same outcome, happier team :)&lt;/p&gt;

&lt;p&gt;The effort to setup a CI process that ensures your codebase compiles is less than half a day work. It's worth the effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Static code analysis
&lt;/h2&gt;

&lt;p&gt;This comes for free in almost every language and is a one liner to run against a predefined set of rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Javascript: &lt;a href="https://eslint.org/"&gt;eslint&lt;/a&gt;, &lt;a href="https://palantir.github.io/tslint/"&gt;tslint&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Java: &lt;a href="https://www.sonarlint.org/"&gt;sonarlint&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python: &lt;a href="https://www.pylint.org/"&gt;pylint&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go: &lt;a href="https://github.com/golang/lint"&gt;golint&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setting up the static code analysis (or linting) takes 1 hour or so. For what benefits?&lt;/p&gt;

&lt;p&gt;You have well formatted and "by the book" code in your main branch. That's a clear quality increase for your code base.&lt;/p&gt;

&lt;p&gt;If that is the least of your problems because your team is always rushing to meet deadlines, think of it this way. Your code review process will be faster. Anything that is in the area of code structure, best practices etc. is already checked by your CI process. No need to review or discuss it. Your developers can focus on the business content of code reviews.&lt;/p&gt;

&lt;p&gt;Great bonus: developers learn the code conventions automatically. The static analysis tool provides the violated rule and a why it is wrong to do so.&lt;/p&gt;

&lt;p&gt;A hurdle with conventions is developers are dogmatic about tabs VS space kind of things. At the end of the day good conventions are ones followed by all. Pick a set of standard conventions and roll with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Culture change
&lt;/h2&gt;

&lt;p&gt;Continuous Integration is not a technical problem. It's a team process. You want to work in small increments and integrate code to the main branch often. See &lt;a href="https://fire.ci/blog/how-to-get-started-with-continuous-integration/"&gt;How to get started with CI&lt;/a&gt; for a larger discussion on what the culture goal actually is.&lt;/p&gt;

&lt;p&gt;After the team masters the first critical elements, another shift should happen. People will realize that working in smaller increments is more efficient. Automated checks of basic mistakes will boost confidence to merge code faster. &lt;/p&gt;

&lt;p&gt;As a result branches life span will decrease. Code review will be faster. Every body will work with nearly latest code. It will prevent drifts and merge conflicts due to people working apart. See &lt;a href="https://fire.ci/blog/why-you-should-not-use-feature-branches/"&gt;Why you should not use feature branches&lt;/a&gt; for a full list of benefits.&lt;/p&gt;

&lt;p&gt;At the end of the day  CI works with our human pride and ego.  &lt;/p&gt;

&lt;p&gt;Everybody would be happy for a tool to catch their mistake before it reaches the world. &lt;/p&gt;

&lt;h2&gt;
  
  
  How do you get started?
&lt;/h2&gt;

&lt;p&gt;Here is a very simple and actionable process to get started. It works regardless of your git provider: GitHub, Bitbucket, Gitlab, Azure DevOps, ... and all the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Enable a Pull Request (PR) process
&lt;/h3&gt;

&lt;p&gt;Lock your main branch from direct pushes. Everything should come through PRs. Here are links on how to do this for &lt;a href="https://help.github.com/en/github/administering-a-repository/enabling-branch-restrictions"&gt;Github&lt;/a&gt;, &lt;a href="https://confluence.atlassian.com/bitbucketserver/using-branch-permissions-776639807.html#Usingbranchpermissions-Addbranchpermissionsforasinglerepository"&gt;Bitbucket&lt;/a&gt;, &lt;a href="https://docs.gitlab.com/ee/user/project/protected_branches.html"&gt;GitLab&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops"&gt;Azure DevOps&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Pick a CI platform
&lt;/h3&gt;

&lt;p&gt;Every git provider allows to define build pipelines for your PRs. The builds will run when the PR is created and for each new push to the branch the PR carries. A pre condition to complete your PR (= merge your branch) will be a succesfull build.&lt;/p&gt;

&lt;p&gt;The big CI players are CircleCI, Codeship, Travis CI.&lt;/p&gt;

&lt;p&gt;I of course recommend &lt;a href="https://fire.ci/"&gt;Fire CI&lt;/a&gt; since its the platform I've built. I don't claim it is better than the rest for each and every use cases.&lt;/p&gt;

&lt;p&gt;Pick one and get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Define a 2 liner build
&lt;/h3&gt;

&lt;p&gt;The most basic build we want to achieve is build + static code analysis. Getting there is 2 or 3 commands in a shell.&lt;/p&gt;

&lt;p&gt;All CI platforms go for "configuration as code". You define your build in a *.yml file at the root of your repository and the platform picks it up.&lt;/p&gt;

&lt;p&gt;With Fire CI for example you would need to add a .fire.yml file at the root of your repo that would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline: 
  dockerfile: Dockerfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then you add a file named "Dockerfile" to build your app. Here are a few examples of simple Dockerfiles.&lt;/p&gt;

&lt;p&gt;Any yarn/npm based tech like React/Angular/Vue/Node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3
WORKDIR /app 
COPY . .
RUN yarn
RUN yarn lint
RUN yarn build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3
WORKDIR /app 
COPY . .
RUN pip install all_your_dependencies
RUN pylint all_your_python_files.py 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM golang:latest
WORKDIR /app
COPY . .
RUN go build -o main .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I could go on with many more. These examples are simplisitc and could be improved with a few more commands. But you get the point: it's easy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional: Enable code reviews
&lt;/h3&gt;

&lt;p&gt;Now that every code contribution comes through a PR, code reviews are easy to do. Every git provider has an awesome UI to present the differences and allow you to comment on code.&lt;/p&gt;

&lt;p&gt;If you are new to the process do not define a mandatory set of reviewers as it will slow your team down. Do start a best effort process to review each others code. And build on that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What then?
&lt;/h2&gt;

&lt;p&gt;As with everything, think big but start small. Having a CI process in place opens a world of opportunities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;Once you have the basic process in place, it becomes a breeze to add your first automated test. And then some others. Low yet continuous effort can bring you an awesome test coverage before you know it.&lt;/p&gt;

&lt;p&gt;I recommend you remain lean and not invest effort to "let's write some tests". Check what breaks often or requires a high effort to test manually. Automate that. &lt;/p&gt;

&lt;p&gt;Always keep productivity in mind. Having a ton of tests just because is worth nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other perks
&lt;/h3&gt;

&lt;p&gt;There are many tools out there that you can integrate to your CI process. They are not key but the effort VS benefits could be worthwile.&lt;/p&gt;

&lt;p&gt;A few examples below. Links are for the GitHub marketplace but other git providers integrate as easily.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Automatic update of dependencies: &lt;a href="https://github.com/marketplace/depfu"&gt;Depfu&lt;/a&gt; suggests you dependencies updates automatically. This way you remain up to date doing small increments. Always better than a once a year "let's bump everything" strategy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open source security: &lt;a href="https://github.com/marketplace/snyk"&gt;Snyk&lt;/a&gt; warns you about security threats in open source libraries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Images optimisation: &lt;a href="https://github.com/marketplace/imgbot"&gt;ImgBot&lt;/a&gt; detects large images in your repository. And submits a PR with size optimized version. Relevant for frontend projects, but still nice.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many more out there. Browse the marketplace for things that could solve a problem for you.&lt;/p&gt;

&lt;p&gt;Careful though! Resist the urge to use everything that comes to mind. Pick the ones that really provide a productivity boost. Free metrics or tools that are not looked at are harmful as people are not sure what to do with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You do not need fancy tests suites to get started with Continuous Integration. &lt;/p&gt;

&lt;p&gt;Literally 2 hours of effort can get you rolling. And enable a virtuous circle for your team productivity.&lt;/p&gt;

&lt;p&gt;The bigger your team and your projects, the greater the benefits. In 2020 there is no good reason to not have a CI process.&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://twitter.com/jpdelimat"&gt;contact me&lt;/a&gt; if you need help setting up a CI process for your team. I'll be happy to help if I can.&lt;/p&gt;

&lt;p&gt;Thanks for reading and good luck!&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://fire.ci/blog/"&gt;The Fire CI Blog&lt;/a&gt;.&lt;br&gt;
(the awesome cover photo is from &lt;a href="https://unsplash.com/photos/DH5183gvKUg"&gt;Will Porada on Unsplash&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>ci</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Top Five Developer Skills That'll Make You a Hero (Hint: Involves LEGOs)</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Tue, 28 Jan 2020 07:27:32 +0000</pubDate>
      <link>https://dev.to/jpdelimat/the-top-five-developer-skills-that-ll-make-you-a-hero-hint-involves-legos-5ff7</link>
      <guid>https://dev.to/jpdelimat/the-top-five-developer-skills-that-ll-make-you-a-hero-hint-involves-legos-5ff7</guid>
      <description>&lt;p&gt;&lt;em&gt;Programming is like building Lego bricks. Any developer can pick a brand new Lego set and build it following the instructions. This is very easy. Think of it as coding school assignments or entry level tutorials.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A real software project is different. It is like building a very large castle set. Except it was already built in the past. Then someone torn it to pieces with a savage kick. The bigger parts remained kind of together. Smaller parts were completely crushed. Some elements went out the window.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You get a box containing what's left, but also thousands of pieces from other sets. And the instructions are gone of course. You can only rely on one picture to know what the castle looks like.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that's interesting! Let's see how to work effectively in this environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Accept the unknowns
&lt;/h2&gt;

&lt;p&gt;The first step of any development task is to understand what you need to do. It sounds obvious. And yet, the bigger the task, the more unknown variables you get. This is the moment to set clear expectations. &lt;/p&gt;

&lt;p&gt;If you have no clue how to get started, there is no point thinking and conceptualizing too long. Start and get your head around the key elements you need. Then think again. If anyone asks you for estimates or a deadline upfront, be open and honest. Some parts of the system you don't know or may not understand. And that's OK. &lt;/p&gt;

&lt;p&gt;Think of platforms like Facebook, Netflix or Oracle. Or any larger enterprise software out there. Very few people can grasp the full scope. The ones who do have either built it or spent years working with it. So first of all, give yourself a break for not knowing everything. And more importantly, accept that you won't know everything.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Experienced and productive developers are not better coders. They are better at evaluating what they need to do. And at picking the right strategies to cope with the unknowns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Lego analogy&lt;/strong&gt;: Think of the castle set we want to rebuild. Say someone gives you a picture of the castle and the box, and asks you "how much time do you need to build the castle?". There is no good answer to that except "I don't know yet. Let me start and let's see where I am at after a day or two".&lt;/p&gt;

&lt;p&gt;The ultimate "I fear the unknowns" approach to this task would be to lay out all the elements from the box on the floor. Try to separate them into the sets they belong to based on color and shape. Then look at the picture and make a mental map on how to assemble the bricks.&lt;/p&gt;

&lt;p&gt;This approach is wrong for 2 reasons. First, if the castle is too big you'll probably never get there. Second and most important: you can't assess progress. You could be on the right track or not at all. You have no feedback loop.&lt;/p&gt;

&lt;p&gt;Another approach would be to start building the castle. As you go you will learn if it is easy to find the pieces you need or not. You will know if the picture shows all the details or if the construction is more complex than it looks. &lt;/p&gt;

&lt;p&gt;Based on these information you'll be able to make a more educated guess on the time and effort it will take to do the job. And if it's worth doing at all. If you need it built for tomorrow morning, maybe going to the store and buying the same castle again is a better solution? That's might not be your decision to make, but solutions to a problem does not always come through more code.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Accept compromise
&lt;/h2&gt;

&lt;p&gt;Developers often praise and value "great attention to details". Every job offer out there has this in one form or the other. That's all good. But don't confuse attention to details with stubbornness to have everything perfect.&lt;/p&gt;

&lt;p&gt;When starting a big task you have to define 2 versions of it. The first version is the bare minimum you need to verify that things will work the way you think they will.&lt;/p&gt;

&lt;p&gt;That's what I call "working horizontally". Each piece of your bigger task is a vertical. You don't have to go deep to get the first results. Focus on the main use case or scenario first. And take it from there.&lt;/p&gt;

&lt;p&gt;The things you can leave behind in the first version: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  error handling&lt;/li&gt;
&lt;li&gt;  edge cases&lt;/li&gt;
&lt;li&gt;  clean code&lt;/li&gt;
&lt;li&gt;  configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't think of all that and just write the code you need as it flows from your fingers. You should get to a high functional coverage quickly. Sure, it will take a lot of time to move from this first, simplistic version to the final one. So what is the point?  &lt;/p&gt;

&lt;p&gt;That's a safer way to progress. You want to verify your assumptions. As smart and capable as you may be, designing and conceptualizing can only take you that far. Get your hands dirty. It will give you more knowledge and insight than any "thinking it through".&lt;/p&gt;

&lt;p&gt;This is the same principle that applies to MVPs for new products or features in business. The approach also has the advantage that you can show your first version and get feedback. Or ask questions. It's easier to brainstorm on existing code than on a drawing or concept.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lego analogy:&lt;/strong&gt; You are building the tower of the castle. On the picture the high tower walls is made of interlaced grey and white bricks. There is a princess locked in the tower and a dragon on the roof.&lt;/p&gt;

&lt;p&gt;You find the princess and the dragon but it would take you ages to find the grey and white bricks you need. The right approach is to build the wall using any bricks, and place the princess and the dragon. You can leave a TODO like "Improve the bricks of the wall".&lt;/p&gt;

&lt;p&gt;The idea is that you have identified a problem: it's going to be difficult to build the perfect wall. Let's accept that and move on to discover all the other obstacles we don't know about yet. Accepting the compromise prevents you from getting stuck.&lt;/p&gt;

&lt;p&gt;Even if you never get to the TODO, you can tell your customer: "Here is the full castle. We know we have to improve the tower wall, but it's built". This is much better than "We are heavily delayed because it took us ages to find the right bricks for the tower. But look, the tower is perfect and exactly as on the picture you sent us. Now we'll get to the rest of the castle".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important: don't confuse compromise and sloppiness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key elements of the tower are the princess and the dragon. Don't put a farmer in the tower and a cat on the roof and think that's an OK compromise. It won't work :)&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Start with the outside world
&lt;/h2&gt;

&lt;p&gt;There are many things you can control. And things you can't. I've learnt it the hard way on one of my first assignments as a developer ten years ago. The task was to integrate an external API and process the data. I had one week. It was a very reasonable timeline, even for an inexperienced guy like me.&lt;/p&gt;

&lt;p&gt;Here is what I've done (and you shouldn't).&lt;/p&gt;

&lt;p&gt;It's Monday morning. I've read the API documentation for 10 minutes. It seems very easy. I create a test data set and moving on to write the code to process it. I will test with the real API once I'm done.&lt;/p&gt;

&lt;p&gt;Wednesday morning and I'm almost done. I think my code is clean and well designed and everything (it wasn't). Now I just need to integrate the API and I'll probably finish ahead of time. I can't help to think that "I am awesome".&lt;/p&gt;

&lt;p&gt;I quickly get to the API part. I'm trying to reach it with my code. Except I can't. Something is wrong. I waste the whole day double checking everything. Using different API clients. No progress. The day flashes and it is Wednesday evening now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am stuck, and I feel quite the opposite of awesome.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I get to work on Thursday and ask a colleague for help. He tells me that access to the API may be IP restricted and that I have to contact the company to white-list our IPs. Good, I have a way forward.&lt;/p&gt;

&lt;p&gt;I send an email to the company owning the API. It's like 8AM. I foolishly expect a quick answer and resolution within minutes. I sweat all morning and at noon I finally pick up the phone and call the support number. I explain my problem and try to highlight the best I can how big an "emergency" it is (it wasn't).&lt;/p&gt;

&lt;p&gt;The guy on the other side of the line explains me that the white-listing time is usually 1 or 2 days. Now I am depressed. 1 or 2 days? How is this possible? My task is the most important in the world (only to me of course) and they tell me "1 or 2 days"? &lt;/p&gt;

&lt;p&gt;All the sudden I am not ahead anymore. I am late. I failed. I go to my boss and tell him I fucked up. I should have checked the API Monday morning. I would then have requested access the same day and I would have written my code in the meantime. He just smiles back as a "Yes you should have".&lt;/p&gt;

&lt;p&gt;I finally get access on Friday and have to stay very late to finish the job. I adapt my code to the many surprises the API data bring. Good bye well designed and clean code. I will justified it later saying "There was no time for that" (there was).&lt;/p&gt;

&lt;p&gt;In my naiveness of the time, I felt the access thing and wrong documentation were very bad luck. Now I can tell it is business as usual.&lt;/p&gt;

&lt;p&gt;The lesson is to start with what you can't control. Confirm every assumption you have about the environment. Use manual and low costs ways to try things out as early as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lego analogy&lt;/strong&gt;: Imagine you are building the castle and things are running smooth. You now have mixed the box about 100 times looking for pieces. You can't help thinking "I never came across that huge orange dragon sitting on the tower on the picture". &lt;/p&gt;

&lt;p&gt;You ignore that information to focus on the good progress you have. That's human. Moving forward is more exciting than dealing with problems. At the end you'll have to acknowledge that the dragon is missing. And tell your customer very late that the bigger piece of the set will not be present. That's not good.&lt;/p&gt;

&lt;p&gt;The thing to do instead is to follow up on that hint: "Where is the dragon?". Spend the time needed to be 100% sure it's not there. Raise the issue right away. Tell your customer "Hey, there is no dragon in the box. I can't make up a dragon out of the other bricks. What do we do?". &lt;/p&gt;

&lt;p&gt;People are surprisingly OK with problems when they know early enough.  Discovering problems early opens more possible solutions. "Shall we continue knowing there is no dragon?". "Can we buy the dragon alone?". "I can see a dinosaur in the box. Can we use it instead?".&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Draw a clear line
&lt;/h2&gt;

&lt;p&gt;When you start working on a new feature for an existing system, start by defining how it interfaces with the existing code. Of course you should try and follow SOLID principles etc. but the key part is simpler than that. Just try to make the touching surface as low as possible.&lt;/p&gt;

&lt;p&gt;The simple process of clearly defining the cut will improve your solution. It will force you force you to tackle the key questions: &lt;em&gt;How will the users or system use my code?&lt;/em&gt; &lt;em&gt;What inputs will I get? What outputs should I produce?&lt;/em&gt; It helps you keep your eyes on the ball. This is even more true if you don't know much about the system you are working with yet. It is a good opportunity to explore the unknowns before diving into what you know already.&lt;/p&gt;

&lt;p&gt;It also makes it easy to turn the feature on or off. You can use a boolean flag or a more advance feature toggle mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lego analogy&lt;/strong&gt;: Say you need to build an extension of the castle. Requirements are rather high level so there is plenty of room for creativity. You can't touch the existing castle though. &lt;/p&gt;

&lt;p&gt;You could go and build a great extension only to find out there is no space to attach it to the castle anywhere. That's unfortunate. You'll have to quickly alter your extension to make it fit somehow. &lt;/p&gt;

&lt;p&gt;The right approach would be to think of the touching surface first. Where will the extension be on the castle. What bricks can I attach it to? What form do they have? Put together the few bricks of the extension attaching it to the castle. Verify they plug for a solid connection. From there you can freestyle any extension you want. &lt;/p&gt;

&lt;h2&gt;
  
  
  5. Don't be too DRY
&lt;/h2&gt;

&lt;p&gt;DRY stands for Don't Repeat Yourself. That is probably the easiest rule to follow. As soon as you see duplicated lines of code, you make an abstraction. It can be a base class, a helper method, whatever.&lt;/p&gt;

&lt;p&gt;What happens then? The next person comes and the common code needs to change to cover more cases. She or he adds parameters and if statements to cope with the emerging complexities. Soon, the 5 initial and simple lines become 30 lines and it's difficult to figure out what is happening. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Poor readability is not a good trade for code repetitions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It would have been better to keep the duplicated lines. You could then change each instance at will.&lt;/p&gt;

&lt;p&gt;The next time you reach for "abstraction over repetition", ask yourself: how many times did you see someone go back from an abstraction? Like removing a base class and putting the common code back to the inheriting classes. I bet the answer is never. &lt;/p&gt;

&lt;p&gt;The reason is abstractions and design patterns in general are cool and sophisticated. And if one exists then "there must be a good reason". So once you introduce the abstraction, there is a good chance it will stay there forever.&lt;/p&gt;

&lt;p&gt;Does this mean you should never use abstractions? No. User abstractions when they fit requirements. Things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  "We want to log every call to this method with inputs outputs"&lt;/li&gt;
&lt;li&gt;  "We want to log every HTTP requests with data a, b, c"&lt;/li&gt;
&lt;li&gt;  "Every time a user is created we need to do this and that&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are good candidates for abstraction and there are many more examples, but notice how the requirements are more technical than business related (logging, security, analytics, etc.). It is rare that abstraction friendly requirements are part of your business domain. &lt;/p&gt;

&lt;p&gt;Why? Because the business domain is close to the real world. And that we can't control. Assumptions made at the beginning of a project often fall short pretty soon. Don't over engineer code just to avoid repetition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lego analogy&lt;/strong&gt;: None. There is no DRY concept in Lego bricks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Working smart is not about better code. It is about figuring out what needs to be done, and safely progressing toward the goal. &lt;/p&gt;

&lt;p&gt;Large and challenging development tasks will carry unknowns. Embrace it. Learn to work with it.&lt;/p&gt;

&lt;p&gt;You will be more productive if you keep things simple and align expectations for the outcome with your team, boss, customer, ideally everybody.&lt;/p&gt;

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

&lt;p&gt;Originally published on &lt;a href="https://fire.ci/blog/"&gt;The Fire CI Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The real difference between CI and CD</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Sun, 01 Dec 2019 21:24:07 +0000</pubDate>
      <link>https://dev.to/jpdelimat/the-real-difference-between-ci-and-cd-3k</link>
      <guid>https://dev.to/jpdelimat/the-real-difference-between-ci-and-cd-3k</guid>
      <description>&lt;p&gt;There is plenty of content out there describing what Continuous Integration, Continuous Delivery and Continuous Deployment are. But what are these processes for in the first place? &lt;/p&gt;

&lt;p&gt;It is crucial to understand the problems CI and CD solve to use them properly. It will allow your team to improve your process. And avoid putting effort chasing fancy metrics that do not bring any value to your process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Integration is a team problem
&lt;/h2&gt;

&lt;p&gt;If you work in a team you are several developers working on the same repository. There is a main branch in the repository carrying the latest version of the code. Developers work on different things on different branches. Once someone is done with his change, he’ll push or merge it to the main branch. Eventually the whole team will pull this change.&lt;/p&gt;

&lt;p&gt;The scenario we want to avoid is that a faulty commit makes it to the main branch. Faulty means the code does not compile or the app won't start or is unusable. &lt;br&gt;
Why? Not because the app is broken or because all tests must always be green. That is not a problem, you can very well never deploy that version and wait for a fix. &lt;/p&gt;

&lt;p&gt;The problem is that your entire team is stuck. All the developers who pulled the faulty commit will spend 5 minutes wondering why it doesn't work. Several will probably try to find the faulty commit. Some will try to fix the issue by themselves in parallel of the faulty code author.&lt;/p&gt;

&lt;p&gt;This is a waste of time for your team. The worst part is that repeated incidents fuel a mistrust of the main branch and encourage developers to work apart.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Continuous Integration is all about preventing the main branch of being broken so your team is not stuck. That's it. It is &lt;strong&gt;not&lt;/strong&gt; about having all your tests green all the time and the main branch deployable to production at every commit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The process of Continuous Integration is independent of any tool. You could verify manually that the merge of your branch and the main branch works locally. And then only actually push the merge to the repository. Now that would be very inefficient. That's why Continuous Integration is implemented using automated checks.&lt;/p&gt;

&lt;p&gt;The checks shall ensure the bare minimum: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The app should build and start&lt;/li&gt;
&lt;li&gt;Most critical features should be functional at all times (user signup/login journey and  key business features)&lt;/li&gt;
&lt;li&gt;Common layers of the application that all the developers rely on, should be stable. This means unit tests on those parts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice it means you need to pull any unit test framework that work for you and secure the common layers of the application. Sometimes it is not that much code and can be done fairly quickly. Also you need to add a "smoke test" verifying that the code compiles and that the application starts. This is especially important in technologies with crazy dependency injections like Java Spring or .NET core. In large projects it is so easy to miswire your dependencies that verifying that the app actually always at least starts is a must.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have hundreds or thousands of tests you don't need to run them all for each merge. It will take a lot of time and most tests probably verify "non team blocker" features.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll see in the next sections how the process of Continuous Delivery will make good use of these many tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's not about tools
&lt;/h3&gt;

&lt;p&gt;Tools and automated checks are all fine. But if your developers only merge giant branches they work on for weeks, they won't help you. The team will spend a good amount of time merging the branches and fixing the code incompatibilities that will arise eventually. It is as much a waste of time as being blocked by a faulty commit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Continuous Integration is not about tools. It is about working in small chunks and integrating your new code to the main branch and pulling frequently. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Frequently means at least daily. Split the task you are working on into smaller tasks. Merge your code very often and pull very often. This way nobody works apart for more than a day or two and problems do not have time to become snowballs.&lt;/p&gt;

&lt;p&gt;A large task does not need to be all in one branch. It should never be. Techniques to merge work in progress to the main branch are called "branching by abstraction" and "feature toggles". See the blog post &lt;a href="https://fire.ci/blog/how-to-get-started-with-continuous-integration/"&gt;How to get started with Continuous Integration&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key points for a good CI build
&lt;/h3&gt;

&lt;p&gt;It's very simple. &lt;strong&gt;Keep it short. 3-7 minutes should be max.&lt;/strong&gt; It's not about CPU and resources. It is about developers' productivity.&lt;br&gt;
The first rule of productivity is focus. Do one thing, finish it, then move to the next thing. &lt;/p&gt;

&lt;p&gt;Context switching is costly. Studies show it takes ~23 minutes to deeply refocus on something when you get disturbed. Imagine you push your branch to merge it.&lt;br&gt;
You start another task. You spend 15-20 minutes getting into it. The minute after you are in the zone you receive a "build failed" notification from your 20 minutes long CI build for the previous task. You get back to fix it. You push it again. You easily lost more than 20 minutes moving back and forth.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Multiply 20 minutes once or twice a day by the number of developers in your team ... That's a lot of precious time wasted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now imagine the feedback came within 3 minutes. And you knew it would. You probably wouldn't have started the new task at all. You would have proof read your code one more time or reviewed a PR while waiting. The failed notification would come. You would fix it. And then move on to the next task. That is the kind of focus your process should enable.&lt;/p&gt;

&lt;p&gt;Keeping your CI build short makes it a trade off. Tests that run longer or provide little value in the context of CI should be moved to the CD step. And yes, failures there also need to be fixed. But since they are not preventing anybody from doing their thing, you can take the fixes as a "next task" when you finish what you are doing. Just turn off the notifications while working and check every now and then. Keep the context switching to its minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous Delivery and Deployment are engineering problems
&lt;/h2&gt;

&lt;p&gt;Let’s settle on the definitions to get that out of the way.&lt;/p&gt;

&lt;p&gt;Continuous Delivery is about being able to deploy any version of your code at all times. In practice it means the last or pre last version of your code. You don’t deploy automatically, usually because you don’t have to or are limited by your project lifecycle. But as soon as someone feels like it, deploy can be done in minimum time. That someone can be the test/QA team that wants to test things out on a staging or preproduction environment. Or it can actually be time to roll out the code to production.&lt;/p&gt;

&lt;p&gt;The idea of Continuous Delivery is to prepare artefacts as close as possible from what you want to run in your environment. These can be jar or war files if you are working with Java, executables if you are working with .NET. These can also be folders of transpiled JS code or even Docker containers, whatever makes deploy shorter (i.e. you have pre built in advance as much as you can).&lt;/p&gt;

&lt;p&gt;By preparing artefacts I don't mean turning code into artefacts. This is usually a few scripts and minutes of execution. Preparing means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run all the tests you can to ensure that once deployed the code will actually work. Run unit tests, integration tests, end to end tests and even performance tests if you can automate that. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This way you can filter which versions of your main branch are actually production ready and which are not. The ideal test suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures that the application key functionalities work. Ideally all functionalities&lt;/li&gt;
&lt;li&gt;Ensures that no performance deal breaker has been introduced so when your new version hits your many users it has a chance to last&lt;/li&gt;
&lt;li&gt;Dry run any database updates your code needs to avoid surprises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not need to be very fast. 30 minutes or 1 hour is acceptable.&lt;/p&gt;

&lt;p&gt;Continuous Deployment is the next step. You deploy the most up to date and production ready version of your code to some environment. Ideally production if you trust your CD test suite enough. &lt;/p&gt;

&lt;p&gt;Note that depending on the context this is not always possible or worth the effort. Continuous Delivery is often enough to be productive. Especially if you are working in a close network and have limited environments you can deploy to. It can also be that the release cycle of your software prevents unplanned deploys.&lt;/p&gt;

&lt;p&gt;Continuous Delivery and Continuous Deployment (let’s call them CD from now on) are not team problems. They are about finding the right balance between execution time, maintenance efforts and relevance of your tests suite to be able to say "This version works as it should". And it is a balance. If your tests last 30 hours that is a problem. See &lt;a href="https://news.ycombinator.com/item?id=18442941"&gt;this epic post&lt;/a&gt; about how the Oracle database test suite looks like. If you spend so much time keeping your tests up to date with latest code that it impedes the team progress, that is not good either. And if your test suite ensures pretty much nothing ... it is basically useless.&lt;/p&gt;

&lt;p&gt;In an ideal world we want 1 set of deployable artefacts per commit to the main branch. You can see we have a vertical scalability problem: the faster we move from code to artefacts, the more ready we are to deploy the newest version of the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the big difference?
&lt;/h2&gt;

&lt;p&gt;Continuous Integration is an horizontal scalability problem. You want developers to merge their code often so the checks must be fast. Ideally within minutes to avoid developers switching context all the time with highly async feedback from the CI builds. &lt;/p&gt;

&lt;p&gt;The more developers you have, the more computing power you need to run simple checks (build and test) on all the active branches.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A good CI build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures no code that breaks basic stuff and prevents other team members to work is introduced to the main branch &lt;/li&gt;
&lt;li&gt;Is fast enough to provide feedback to developers within minutes to prevent context switching between tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Continuous Delivery and Deployment are vertical scalability problems. You have one rather complex operation to perform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A good CD build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures that as many features as possible are working properly&lt;/li&gt;
&lt;li&gt;The faster the better, but it is not a matter of speed. A 30-60 minutes build is OK&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;A common misconception is to see CD as a horizontal scalability problem like CI: the faster you can move from code to artefacts, the more commits you can actually process, and the closer to the ideal scenario you can be. But we don't need that. Producing artefacts for every commit and as fast as possible is usually overkill. You can very well approach CD on a best effort basis: have a single CD build that will just pick the latest commit to verify once a given build is finished.&lt;/p&gt;

&lt;p&gt;Make no mistake about CD. It is really hard. Getting to sufficient test confidence to say your software is ready to be deployed automatically usually works on low surface applications like APIs or simple UIs. It is very difficult to achieve on a complex UI or a large monolith system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Tools and principles used to execute CI and CD are often very similar. The goals are very different though. &lt;/p&gt;

&lt;p&gt;Continuous Integration is a trade off between speed of feedback loop to developers and relevance of the checks your perform (build and test). No code that would impede the team progress should make it to the main branch. &lt;/p&gt;

&lt;p&gt;Continuous Delivery of Deployment is about running as thorough checks as you can to catch issues on your code. Completeness of the checks is the most important factor. It is usually measured in terms code coverage or functional coverage of your tests. Catching errors early on prevents broken code to get deployed to any environment and saves the precious time of your test team.&lt;/p&gt;

&lt;p&gt;Craft your CI and CD builds to achieve these goals and keep your team productive. No workflow is perfect. Problems will happen every now and then. Use them as lessons learned to strengthen your workflow every time they do.&lt;/p&gt;

&lt;p&gt;Published on 27 Nov 2019 by Jean-Paul Delimat on the &lt;a href="https://fire.ci/blog"&gt;Fire CI Blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ci</category>
      <category>continuousdelivery</category>
      <category>continuousdeployment</category>
    </item>
    <item>
      <title>API end to end testing with Docker</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Tue, 12 Nov 2019 15:24:34 +0000</pubDate>
      <link>https://dev.to/jpdelimat/api-end-to-end-testing-with-docker-4a0b</link>
      <guid>https://dev.to/jpdelimat/api-end-to-end-testing-with-docker-4a0b</guid>
      <description>&lt;p&gt;Testing is a pain in general. Some don't see the point. Some see it but think of it as an extra step slowing them down. Sometimes tests are there but very long to run or unstable. In this article you'll see how&lt;br&gt;
you can engineer tests for yourself with Docker. &lt;/p&gt;

&lt;p&gt;We want fast, meaningful and reliable tests written and maintained with minimal effort. It means tests that are useful to you as a developer on a day-to-day basis. They should boost your productivity and improve the quality of your software. Having tests because everybody says  "you should have tests" is no good if it slows you down.&lt;/p&gt;

&lt;p&gt;Let's see how to achieve this with not that much effort.&lt;/p&gt;
&lt;h2&gt;
  
  
  The example we are going to test
&lt;/h2&gt;

&lt;p&gt;In this article we are going to test an API built with Node/express and use chai/mocha for testing. I've chosen a JS'y stack because the code is super short and easy to read. The principles applied are valid for any tech stack. Keep reading even if Javascript makes you sick.&lt;/p&gt;

&lt;p&gt;The example will cover a simple set of CRUD endpoints for users. It's more than enough to grasp the concept and apply to the more complex business logic of your API.&lt;/p&gt;

&lt;p&gt;We are going to use a pretty standard environment for the API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Postgres database&lt;/li&gt;
&lt;li&gt;A Redis cluster&lt;/li&gt;
&lt;li&gt;Our API will use other external APIs to do its job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your API might need a different environment. The principles applied in this article will remain the same. You'll use different Docker base images to run whatever component you might need.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Docker? And in fact Docker Compose
&lt;/h2&gt;

&lt;p&gt;This section is a lot of arguments in favour or using Docker for testing. You can skip it if you want to get to the technical part right away.&lt;/p&gt;
&lt;h3&gt;
  
  
  The painful alternatives
&lt;/h3&gt;

&lt;p&gt;To test your API in a close to production environment you have two choices. You can mock the environment at code level or run the tests on a real server with the database etc. installed.&lt;br&gt;
Mocking everything at code level clutters the code and configuration of our API. It is also often not very representative of how the API will behave in production. Running the thing in a real server is infrastructure heavy. It is a lot of setup and maintenance, and it does not scale. Having a shared database, you can run only 1 test at a time to ensure test runs do not interfere with each other.&lt;/p&gt;

&lt;p&gt;Docker Compose allows us to get the best of both worlds. It creates "containerized" versions of all the external parts we use. It is mocking but on the outside of our code. Our API thinks it is in a real physical environment. Docker compose will also create an isolated network for all the containers for a given test run. This allows you to run several of them in parallel on your local computer or a CI host.&lt;/p&gt;
&lt;h3&gt;
  
  
  Overkill?
&lt;/h3&gt;

&lt;p&gt;You might wonder if it isn't overkill to go end to end tests at all with Docker compose. What about just running unit tests instead?&lt;/p&gt;

&lt;p&gt;For the last 10 years, large monolith applications are being split into smaller services (trending towards the buzzy "micro services"). A given API component relies on more external parts (infrastructure or other APIs). As services get smaller, integration with the infrastructure becomes a bigger part of the job. You should keep a small gap between your production and your development environments. Otherwise problems will arise when going for production deploy. By definition these problems appear at the worst possible moment. They will lead to rushed fixes, drops in quality, and frustration for the team. Nobody wants that.&lt;/p&gt;

&lt;p&gt;You might wonder if end to end tests with Docker compose run longer than traditional unit tests. Not really. You'll see in the example below that we can easily keep the tests under 1 minute. At great benefits: the tests reflect the application behaviour in the real world. This is more valuable than knowing if your class somewhere in the middle of the app works OK or not. Also, if you don't have any tests right now, starting from end to end gives you great benefits for little effort. You'll know all stacks of the application work together for the most common scenarios. That's already something! From there you can always refine a strategy to unit tests critical parts of your application.&lt;/p&gt;
&lt;h2&gt;
  
  
  Our first test
&lt;/h2&gt;

&lt;p&gt;Let’s start with the easiest part. Our API and the Postgres database. And let’s run a simple CRUD test. Once we have that framework in place, we can add more features both to our component and to the test.&lt;/p&gt;

&lt;p&gt;Here is our minimal API with a GET/POST to create and list users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bodyParser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body-parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... validate inputs here ...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;userData&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error: Unable to create user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firstname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&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="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unable to fetch users: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Starting web server...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server started on: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&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 are our tests written with chai. The tests create a new user and fetch it back.&lt;br&gt;
You can see that the tests are not coupled in any way with the code of our API.&lt;br&gt;
The &lt;code&gt;SERVER_URL&lt;/code&gt; variable specifies the endpoint to test. It can be a local or a remote environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chaiHttp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chai-http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;should&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SERVER_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APP_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;chai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chaiHttp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEST_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john@doe.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;createdUserId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should create a new user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chai&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;done&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="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should get the created user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chai&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createdUserId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;done&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;Good. Now to test our API let's define a Docker compose environment. A file called &lt;code&gt;docker-compose.yml&lt;/code&gt; will describe the containers Docker needs to run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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.1'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&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;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;john&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecretpassword&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;5432&lt;/span&gt;

  &lt;span class="na"&gt;myapp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn start&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;APP_DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="na"&gt;APP_DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;john&lt;/span&gt;
      &lt;span class="na"&gt;APP_DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecretpassword&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;8000&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;db&lt;/span&gt;

  &lt;span class="na"&gt;myapp-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerize&lt;/span&gt;
        &lt;span class="s"&gt;-wait tcp://db:5432 -wait tcp://myapp:8000 -timeout 10s&lt;/span&gt;
        &lt;span class="s"&gt;bash -c "node db/init.js &amp;amp;&amp;amp; yarn test"&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;APP_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://myapp:8000&lt;/span&gt;
      &lt;span class="na"&gt;APP_DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="na"&gt;APP_DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;john&lt;/span&gt;
      &lt;span class="na"&gt;APP_DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecretpassword&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;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So what do we have here. There are 3 containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;db&lt;/strong&gt; spins up a fresh instance of PostgreSQL. We use the public Postgres image from Docker Hub. We set the database username and password. We tell Docker to expose the port 5432 the database will listen to so other containers can connect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;myapp&lt;/strong&gt; is the container that will run our API. The &lt;code&gt;build&lt;/code&gt; command tells Docker to actually build the container image from our source. The rest is like the db container: environment variables and ports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;myapp-tests&lt;/strong&gt; is the container that will execute our tests. It will use the same image that myapp because the code will already be there so there is not need to build it again. The command &lt;code&gt;node db/init.js &amp;amp;&amp;amp; yarn test&lt;/code&gt; ran on the container will initialize the database (create tables etc.) and run the tests. We use dockerize to wait for all the required servers to be up and running. The &lt;code&gt;depends_on&lt;/code&gt; options will ensure that containers start in a certain order. It does not ensure that the database inside the db container is actually ready to accept connections. Nor that our API server is already up. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The definition of the environment is like 20 lines of very easy to understand code. The only brainy part is the environment definition. User names, passwords and URLs must be consistent so containers can actually work together.&lt;/p&gt;

&lt;p&gt;One thing to notice is that Docker compose will set the host of the containers it creates to the name of the container. So the database won't be available under &lt;code&gt;localhost:5432&lt;/code&gt; but &lt;code&gt;db:5432&lt;/code&gt;. The same way our API will served under &lt;code&gt;myapp:8000&lt;/code&gt;. There is no localhost of any kind here. This means that your API must support environment variables when it comes to environment definition. No hardcoded stuff. But that has nothing to do with Docker or this article. A configurable application is point 3 of the &lt;a href="https://12factor.net/"&gt;12 factor app manifesto&lt;/a&gt;, so you should be doing it already.&lt;/p&gt;

&lt;p&gt;The very last thing we need to tell Docker is how to actually build the container &lt;strong&gt;myapp&lt;/strong&gt;. We use a Dockerfile like below. The content is specific to your tech stack but the idea is to bundle your API into a runnable server. &lt;/p&gt;

&lt;p&gt;The example below for our Node API installs Dockerize, installs the API depencencies and copies the code of the API inside the container (the server is written in raw JS so no need to compile it).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node AS base&lt;/span&gt;

&lt;span class="c"&gt;# Dockerize is needed to sync containers startup&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DOCKERIZE_VERSION v0.6.0&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;wget https://github.com/jwilder/dockerize/releases/download/&lt;span class="nv"&gt;$DOCKERIZE_VERSION&lt;/span&gt;/dockerize-alpine-linux-amd64-&lt;span class="nv"&gt;$DOCKERIZE_VERSION&lt;/span&gt;.tar.gz &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/bin &lt;span class="nt"&gt;-xzvf&lt;/span&gt; dockerize-alpine-linux-amd64-&lt;span class="nv"&gt;$DOCKERIZE_VERSION&lt;/span&gt;.tar.gz &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm &lt;/span&gt;dockerize-alpine-linux-amd64-&lt;span class="nv"&gt;$DOCKERIZE_VERSION&lt;/span&gt;.tar.gz

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/app

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; ~/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; yarn.lock .&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; base AS dependencies&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;yarn

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; dependencies AS runtime&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Typically from the line &lt;code&gt;WORKDIR ~/app&lt;/code&gt; and below you would run commands that would build your application.&lt;/p&gt;

&lt;p&gt;And here is the command we use to run the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;--abort-on-container-exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This command will tell Docker compose to spin up the components defined in our &lt;code&gt;docker-compose.yml&lt;/code&gt; file. The &lt;code&gt;--build&lt;/code&gt; flag will trigger the build of the myapp container by executing the content of the &lt;code&gt;Dockerfile&lt;/code&gt; above. The &lt;code&gt;--abort-on-container-exit&lt;/code&gt; will tell Docker compose to shutdown the environment as soon as one container exits. That works well since the only component meant to exit is the test container &lt;code&gt;myapp-tests&lt;/code&gt; after the tests are executed. Cherry on the cake, the docker-compose command will exit with the same exit code as the container that triggered the exit. It means that we can check if the tests succeeded or not from the command line. This is very useful for automated builds in a CI environment. &lt;/p&gt;

&lt;p&gt;Isn't that the perfect test setup?&lt;/p&gt;

&lt;p&gt;The full example is &lt;a href="https://github.com/fire-ci/tuto-api-e2e-testing"&gt;here on GitHub&lt;/a&gt;.&lt;br&gt;
You can clone the repository and run the docker compose command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt; &lt;span class="nt"&gt;--abort-on-container-exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Of course you need Docker installed. Docker has the troublesome tendency of forcing you to sign up for an account just to download the thing. But you actually don't have to. Go to the release notes (&lt;a href="https://docs.docker.com/docker-for-windows/release-notes/"&gt;link for Windows&lt;/a&gt; and &lt;a href="https://docs.docker.com/docker-for-mac/release-notes/"&gt;link for Mac&lt;/a&gt;) and download not the latest version but the one right before. This is a direct download link.&lt;/p&gt;

&lt;p&gt;The very first run of the tests will be longer than usual. It is because Docker will have to download the base images for your containers and cache a few things. Next runs will be much faster.&lt;/p&gt;

&lt;p&gt;Logs from the run will look as below. You can see that Docker is cool enough to put logs from all the components on the same timeline. This is very handy when looking for errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;Creating tuto-api-e2e-testing_db_1    ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating tuto-api-e2e-testing_redis_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating tuto-api-e2e-testing_myapp_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Creating tuto-api-e2e-testing_myapp-tests_1 ... &lt;span class="k"&gt;done
&lt;/span&gt;Attaching to tuto-api-e2e-testing_redis_1, tuto-api-e2e-testing_db_1, tuto-api-e2e-testing_myapp_1, tuto-api-e2e-testing_myapp-tests_1
db_1           | The files belonging to this database system will be owned by user &lt;span class="s2"&gt;"postgres"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
redis_1        | 1:M 09 Nov 2019 21:57:22.161 &lt;span class="k"&gt;*&lt;/span&gt; Running &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;standalone, &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6379.
myapp_1        | yarn run v1.19.0
redis_1        | 1:M 09 Nov 2019 21:57:22.162 &lt;span class="c"&gt;# WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.&lt;/span&gt;
redis_1        | 1:M 09 Nov 2019 21:57:22.162 &lt;span class="c"&gt;# Server initialized&lt;/span&gt;
db_1           | This user must also own the server process.
db_1           | 
db_1           | The database cluster will be initialized with locale &lt;span class="s2"&gt;"en_US.utf8"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
db_1           | The default database encoding has accordingly been &lt;span class="nb"&gt;set &lt;/span&gt;to &lt;span class="s2"&gt;"UTF8"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
db_1           | The default text search configuration will be &lt;span class="nb"&gt;set &lt;/span&gt;to &lt;span class="s2"&gt;"english"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
db_1           | 
db_1           | Data page checksums are disabled.
db_1           | 
db_1           | fixing permissions on existing directory /var/lib/postgresql/data ... ok
db_1           | creating subdirectories ... ok
db_1           | selecting dynamic shared memory implementation ... posix
myapp-tests_1  | 2019/11/09 21:57:25 Waiting &lt;span class="k"&gt;for&lt;/span&gt;: tcp://db:5432
myapp-tests_1  | 2019/11/09 21:57:25 Waiting &lt;span class="k"&gt;for&lt;/span&gt;: tcp://redis:6379
myapp-tests_1  | 2019/11/09 21:57:25 Waiting &lt;span class="k"&gt;for&lt;/span&gt;: tcp://myapp:8000
myapp_1        | &lt;span class="nv"&gt;$ &lt;/span&gt;node server.js
redis_1        | 1:M 09 Nov 2019 21:57:22.163 &lt;span class="c"&gt;# WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never &amp;gt; /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.&lt;/span&gt;
db_1           | selecting default max_connections ... 100
myapp_1        | Starting web server...
myapp-tests_1  | 2019/11/09 21:57:25 Connected to tcp://myapp:8000
myapp-tests_1  | 2019/11/09 21:57:25 Connected to tcp://db:5432
redis_1        | 1:M 09 Nov 2019 21:57:22.164 &lt;span class="k"&gt;*&lt;/span&gt; Ready to accept connections
myapp-tests_1  | 2019/11/09 21:57:25 Connected to tcp://redis:6379
myapp_1        | Server started on: 8000
db_1           | selecting default shared_buffers ... 128MB
db_1           | selecting default &lt;span class="nb"&gt;time &lt;/span&gt;zone ... Etc/UTC
db_1           | creating configuration files ... ok
db_1           | running bootstrap script ... ok
db_1           | performing post-bootstrap initialization ... ok
db_1           | syncing data to disk ... ok
db_1           | 
db_1           | 
db_1           | Success. You can now start the database server using:
db_1           | 
db_1           |     pg_ctl &lt;span class="nt"&gt;-D&lt;/span&gt; /var/lib/postgresql/data &lt;span class="nt"&gt;-l&lt;/span&gt; logfile start
db_1           | 
db_1           | initdb: warning: enabling &lt;span class="s2"&gt;"trust"&lt;/span&gt; authentication &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;connections
db_1           | You can change this by editing pg_hba.conf or using the option &lt;span class="nt"&gt;-A&lt;/span&gt;, or
db_1           | &lt;span class="nt"&gt;--auth-local&lt;/span&gt; and &lt;span class="nt"&gt;--auth-host&lt;/span&gt;, the next &lt;span class="nb"&gt;time &lt;/span&gt;you run initdb.
db_1           | waiting &lt;span class="k"&gt;for &lt;/span&gt;server to start....2019-11-09 21:57:24.328 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  starting PostgreSQL 12.0 &lt;span class="o"&gt;(&lt;/span&gt;Debian 12.0-2.pgdg100+1&lt;span class="o"&gt;)&lt;/span&gt; on x86_64-pc-linux-gnu, compiled by gcc &lt;span class="o"&gt;(&lt;/span&gt;Debian 8.3.0-6&lt;span class="o"&gt;)&lt;/span&gt; 8.3.0, 64-bit
db_1           | 2019-11-09 21:57:24.346 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  listening on Unix socket &lt;span class="s2"&gt;"/var/run/postgresql/.s.PGSQL.5432"&lt;/span&gt;
db_1           | 2019-11-09 21:57:24.373 UTC &lt;span class="o"&gt;[&lt;/span&gt;42] LOG:  database system was shut down at 2019-11-09 21:57:23 UTC
db_1           | 2019-11-09 21:57:24.383 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  database system is ready to accept connections
db_1           |  &lt;span class="k"&gt;done
&lt;/span&gt;db_1           | server started
db_1           | CREATE DATABASE
db_1           | 
db_1           | 
db_1           | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/&lt;span class="k"&gt;*&lt;/span&gt;
db_1           | 
db_1           | waiting &lt;span class="k"&gt;for &lt;/span&gt;server to shut down....2019-11-09 21:57:24.907 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  received fast shutdown request
db_1           | 2019-11-09 21:57:24.909 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  aborting any active transactions
db_1           | 2019-11-09 21:57:24.914 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  background worker &lt;span class="s2"&gt;"logical replication launcher"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;PID 48&lt;span class="o"&gt;)&lt;/span&gt; exited with &lt;span class="nb"&gt;exit &lt;/span&gt;code 1
db_1           | 2019-11-09 21:57:24.914 UTC &lt;span class="o"&gt;[&lt;/span&gt;43] LOG:  shutting down
db_1           | 2019-11-09 21:57:24.930 UTC &lt;span class="o"&gt;[&lt;/span&gt;41] LOG:  database system is shut down
db_1           |  &lt;span class="k"&gt;done
&lt;/span&gt;db_1           | server stopped
db_1           | 
db_1           | PostgreSQL init process &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; ready &lt;span class="k"&gt;for &lt;/span&gt;start up.
db_1           | 
db_1           | 2019-11-09 21:57:25.038 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  starting PostgreSQL 12.0 &lt;span class="o"&gt;(&lt;/span&gt;Debian 12.0-2.pgdg100+1&lt;span class="o"&gt;)&lt;/span&gt; on x86_64-pc-linux-gnu, compiled by gcc &lt;span class="o"&gt;(&lt;/span&gt;Debian 8.3.0-6&lt;span class="o"&gt;)&lt;/span&gt; 8.3.0, 64-bit
db_1           | 2019-11-09 21:57:25.039 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv4 address &lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;, port 5432
db_1           | 2019-11-09 21:57:25.039 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv6 address &lt;span class="s2"&gt;"::"&lt;/span&gt;, port 5432
db_1           | 2019-11-09 21:57:25.052 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on Unix socket &lt;span class="s2"&gt;"/var/run/postgresql/.s.PGSQL.5432"&lt;/span&gt;
db_1           | 2019-11-09 21:57:25.071 UTC &lt;span class="o"&gt;[&lt;/span&gt;59] LOG:  database system was shut down at 2019-11-09 21:57:24 UTC
db_1           | 2019-11-09 21:57:25.077 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  database system is ready to accept connections
myapp-tests_1  | Creating tables ...
myapp-tests_1  | Creating table &lt;span class="s1"&gt;'users'&lt;/span&gt;
myapp-tests_1  | Tables created succesfully
myapp-tests_1  | yarn run v1.19.0
myapp-tests_1  | &lt;span class="nv"&gt;$ &lt;/span&gt;mocha &lt;span class="nt"&gt;--timeout&lt;/span&gt; 10000 &lt;span class="nt"&gt;--bail&lt;/span&gt;
myapp-tests_1  | 
myapp-tests_1  | 
myapp-tests_1  |   Users
myapp-tests_1  | Mock server started on port: 8002
myapp-tests_1  |     ✓ should create a new user &lt;span class="o"&gt;(&lt;/span&gt;151ms&lt;span class="o"&gt;)&lt;/span&gt;
myapp-tests_1  |     ✓ should get the created user
myapp-tests_1  |     ✓ should not create user &lt;span class="k"&gt;if &lt;/span&gt;mail is spammy
myapp-tests_1  |     ✓ should not create user &lt;span class="k"&gt;if &lt;/span&gt;spammy mail API is down
myapp-tests_1  | 
myapp-tests_1  | 
myapp-tests_1  |   4 passing &lt;span class="o"&gt;(&lt;/span&gt;234ms&lt;span class="o"&gt;)&lt;/span&gt;
myapp-tests_1  | 
myapp-tests_1  | Done &lt;span class="k"&gt;in &lt;/span&gt;0.88s.
myapp-tests_1  | 2019/11/09 21:57:26 Command finished successfully.
tuto-api-e2e-testing_myapp-tests_1 exited with code 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can see that &lt;strong&gt;db&lt;/strong&gt; is the container that initializes the longest. Makes sense. Once its done the tests start.&lt;br&gt;
The total runtime on my laptop is 16 seconds. Compared to the 880ms used to actually execute the tests it is a lot. In practice tests that run under 1 minute are gold as it is almost immediate feedback. The 15'ish seconds overhead are a buy in time that will be constant as you add more tests. You could add hundreds of tests and still keep execution time under 1 minute.&lt;/p&gt;

&lt;p&gt;Voila! We have our test framework up and running. In a real world project the next steps would be to enhance functional coverage of your API with more tests. Let's consider CRUD operations covered. It's time to add more elements to our test environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding a Redis cluster
&lt;/h2&gt;

&lt;p&gt;Let's add another element to our API environment to understand what it takes. Spoiler alert: it's not much.&lt;/p&gt;

&lt;p&gt;Let us imagine that our API keeps user sessions in a Redis cluster. If you wonder why we would do that, imagine 100 instances of your API in production. Users hit one or another server based on round robin load balancing. Every request needs to be authenticated. It requires user profile data to check for privileges and other application specific business logic. One way to go is to make a round trip to the database to fetch the data every time you need it, but that is not very efficient. Using an in memory database cluster makes the data available across all servers for the cost of a local variable read.&lt;/p&gt;

&lt;p&gt;This is how you enhance your Docker compose test environment with an additional service. &lt;br&gt;
Let’s add a Redis cluster from the official Docker image (I've only kept the new parts of the file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis:alpine"&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;6379&lt;/span&gt;

  &lt;span class="na"&gt;myapp&lt;/span&gt;&lt;span class="pi"&gt;:&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;APP_REDIS_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="na"&gt;APP_REDIS_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;myapp-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dockerize ... -wait tcp://redis:6379 ...&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;APP_REDIS_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="na"&gt;APP_REDIS_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can see it's not much. We added a new container called &lt;code&gt;redis&lt;/code&gt;. It uses the official minimal redis image called &lt;code&gt;redis:alpine&lt;/code&gt;. We added Redis host and port configuration to our API container. And we've made tests wait for it as well as the other containers before executing the tests.&lt;/p&gt;

&lt;p&gt;Let’s modify our application to actually use the Redis cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... validate inputs here ...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Once the user is created store the data in the Redis cluster&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;userData&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error: Unable to create user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;Let's now change our tests to check that the Redis cluster is populated with the right data. That's why the &lt;strong&gt;myapp-tests&lt;/strong&gt; container also gets the Redis host and port configuration in &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should create a new user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chai&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;createdUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createdUserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;cacheData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;cacheData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;done&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;See how easy this was. You can build a complex environment for your tests like you assemble Lego bricks.&lt;/p&gt;

&lt;p&gt;We can see another benefit of this kind of containerized full environment testing. The tests can actually look into the environments components. Our tests can not only check that our API returns the proper response codes and data. We can check that data in the Redis cluster have the proper values. We could also check the database content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding API mocks
&lt;/h2&gt;

&lt;p&gt;A common element for API components is to call other API components.&lt;/p&gt;

&lt;p&gt;Let's say our API needs to check for spammy user emails when creating a user. The check is done using a third party service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateUserEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;externalUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/validate?email=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;valid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... validate inputs here ...&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// We don't just create any user. Spammy emails should be rejected&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValidUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;validateUserEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValidUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;userData&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Error: Unable to create user: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;Now we have a problem for testing anything. We can't create any users if the API to detect spammy emails is not available . Modifying our API to bypass this step in test mode is a dangerous cluttering of the code.&lt;/p&gt;

&lt;p&gt;Even if we could use the real third party service, we don't want to do that. As a general rule our tests should not depend on external infrastructure. First of all, because you will probably run your tests a lot as part of your CI process. It’s not that cool to consume another production API for this purpose. Second of all the API might be temporarily down, failing your tests for the wrong reasons.&lt;/p&gt;

&lt;p&gt;The right solution is to mock the external APIs in our tests. &lt;/p&gt;

&lt;p&gt;No need for any fancy framework. We'll build a generic mock in vanilla JS in ~20 lines of code. This will give us the opportunity to control what the API will return to our component. It allows to test error scenarios.&lt;/p&gt;

&lt;p&gt;Now let’s enhance our tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MOCK_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MOCK_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8002&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Some object to encapsulate attributes of our mock server&lt;/span&gt;
&lt;span class="c1"&gt;// The mock stores all requests it receives in the `requests` property.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;responseBody&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="c1"&gt;// Define which response code and content the mock will be sending&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setupMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Start the mock server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MOCK_SERVER_PORT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Mock server started on port: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MOCK_SERVER_PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Destroy the mock server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teardownMock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server&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="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Our mock is started before any test starts ...&lt;/span&gt;
  &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;initMock&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="c1"&gt;// ... killed after all the tests are executed ...&lt;/span&gt;
  &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;teardownMock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ... and we reset the recorded requests between each test&lt;/span&gt;
  &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]));&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should create a new user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The mock will tell us the email is valid in this test&lt;/span&gt;
    &lt;span class="nx"&gt;setupMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;valid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;chai&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... check response and redis as before&lt;/span&gt;
        &lt;span class="nx"&gt;createdUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Verify that the API called the mocked service with the right parameters&lt;/span&gt;
        &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/validate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;done&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 tests now checks that the external API has been hit with the proper data during the call to our API. &lt;/p&gt;

&lt;p&gt;We can also add other tests checking how our API behaves based on the external API response codes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should not create user if mail is spammy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The mock will tell us the email is NOT valid in this test ...&lt;/span&gt;
    &lt;span class="nx"&gt;setupMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;chai&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... so the API should fail to create the user&lt;/span&gt;
        &lt;span class="c1"&gt;// We could test that the DB and Redis are empty here&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;done&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="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should not create user if spammy mail API is down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The mock will tell us the email checking service&lt;/span&gt;
    &lt;span class="c1"&gt;//  is down for this test ...&lt;/span&gt;
    &lt;span class="nx"&gt;setupMock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

    &lt;span class="nx"&gt;chai&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SERVER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... in that case also a user should not be created&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;done&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;How you handle errors from third party APIs in your application is of course up to you. But you get the point.&lt;/p&gt;

&lt;p&gt;To run these tests we need to tell the container &lt;strong&gt;myapp&lt;/strong&gt; what is the base URL of the third party service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;myapp&lt;/span&gt;&lt;span class="pi"&gt;:&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;APP_EXTERNAL_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://myapp-tests:8002/api&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

  &lt;span class="na"&gt;myapp-tests&lt;/span&gt;&lt;span class="pi"&gt;:&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;MOCK_SERVER_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8002&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion and a few other thoughts
&lt;/h2&gt;

&lt;p&gt;Hopefully this article gave you a taste of what Docker compose can do for you when it comes to API testing. The full example is &lt;a href="https://github.com/fire-ci/tuto-api-e2e-testing"&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Using Docker compose makes tests run fast in an environment close to production. It requires no adaptations to your component code. The only requirement is to support environment variables driven configuration.&lt;/p&gt;

&lt;p&gt;The component logic in this example is very simple but the principles apply to any API. Your tests will just be longer or more complex. They also apply to any tech stack that can be put inside a container (that's all of them). And once you are there you are one step away from deploying your containers to production if need be.&lt;/p&gt;

&lt;p&gt;If you have no tests right now this is how I recommend you should start. End to end testing with Docker compose. It is so simple you could have a first test running in a few hours. Feel free to &lt;a href="https://twitter.com/jpdelimat"&gt;reach out to me&lt;/a&gt; if you have questions or need advice. I'd be happy to help.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article and will start testing your APIs with Docker Compose. Once you have the tests ready you can run them out of the box on our continuous integration platform &lt;a href="https://fire.ci"&gt;Fire CI&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  One last idea to succeed with automated testing.
&lt;/h3&gt;

&lt;p&gt;When it comes to maintaining large test suites, the most important feature is that tests are easy to read and understand. This is key to motivate your team to keep the tests up to date. Complex tests frameworks are unlikely to be properly used in the long run. Regardless of the stack for your API, you might want to consider using chai/mocha to write tests for it. It might seem unusual to have different stacks for runtime code and test code, but if it gets the job done ... As you can see from the examples in this article, testing a REST API with chai/mocha is as simple as it gets. The learning curve is close to zero. So if you have no tests at all and have a REST API to test written in Java, Python, RoR, .NET or whatever other stack, you might consider giving chai/mocha a try.&lt;/p&gt;

&lt;p&gt;If you wonder how to get start with continuous integration at all, I have written a broader guide about it. Here it is: &lt;a href="https://fire.ci/blog/how-to-get-started-with-continuous-integration/"&gt;How to get started with Continuous Integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published on the &lt;a href="https://fire.ci/blog/"&gt;Fire CI Blog&lt;/a&gt; on 06 Nov 2019.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ci</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Essential skills every developer should master (besides coding)</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Wed, 24 Apr 2019 06:59:50 +0000</pubDate>
      <link>https://dev.to/jpdelimat/essential-skills-every-developer-should-master-besides-coding-4e3m</link>
      <guid>https://dev.to/jpdelimat/essential-skills-every-developer-should-master-besides-coding-4e3m</guid>
      <description>&lt;p&gt;Whether you are learning to code, looking for a new job, or just want to improve your skills as a developer, you need to master the essential tools of team collaboration. These are as important as knowing how to code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is teamwork that makes or breaks software projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your code will work eventually. If you want it to work “on time” and be of good quality, you and your team need to be organized.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everyone needs to know what they have to do and when&lt;/li&gt;
&lt;li&gt;People’s work should not overlap or clash&lt;/li&gt;
&lt;li&gt;Common rules need to be followed through by everyone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can achieve this using the right processes and tools. You want your team to focus on coding 90% of the time (the remaining 10% are for coffee breaks and Windows Updates).&lt;/p&gt;

&lt;p&gt;Here are four essential things you need to master.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Git and Pull Requests
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Configuration_management"&gt;Configuration management&lt;/a&gt; is the foundation of team collaboration in software. Many tools exist for it, but luckily one has become the absolute and ultimate reference: &lt;a href="https://git-scm.com/"&gt;Git&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Documentation on the key aspects is well described in the &lt;a href="https://git-scm.com/book/en/v2"&gt;Git Book&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are plenty of turnkey services to get started: &lt;a href="https://github.com/"&gt;GitHub&lt;/a&gt; is probably the most popular, but also &lt;a href="https://bitbucket.org/"&gt;BitBucket&lt;/a&gt; or &lt;a href="https://about.gitlab.com/"&gt;GitLab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A great graphical tool to work with is &lt;a href="https://www.sourcetreeapp.com/"&gt;SourceTree&lt;/a&gt;. I recommend you master the command line before reaching for UI tools, though.&lt;/p&gt;

&lt;p&gt;Below are the questions you should know how to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I merge or rebase my branch, and what is the difference?&lt;/li&gt;
&lt;li&gt;How do I resolve conflicts? For example, mine versus theirs versus manual merge&lt;/li&gt;
&lt;li&gt;What is a &lt;a href="https://bocoup.com/blog/git-workflow-walkthrough-feature-branches"&gt;feature branch&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;What is a &lt;a href="https://yangsu.github.io/pull-request-tutorial/"&gt;pull request&lt;/a&gt;?&lt;/li&gt;
&lt;li&gt;What are the different &lt;a href="https://www.atlassian.com/git/tutorials/comparing-workflows"&gt;collaboration workflows&lt;/a&gt; in Git?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it, and this is pretty straightforward. After reading the theory and with some practice, you will become a Git master in a jiffy.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. The Agile board
&lt;/h1&gt;

&lt;p&gt;The first thing a team needs to do when starting a project or a larger task is to split the job. Over the last 10 years, the “Agile” methodology replaced the traditional waterfall planning. The Agile manifesto is &lt;a href="http://agilemanifesto.org/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Practically speaking it says “let’s not plan too much ahead, because inputs and assumptions we have today about the project will change”. This is almost always true, and nowadays there is hardly any team under the sun that is not (sometimes self-proclaimed) “Agile”.&lt;/p&gt;

&lt;p&gt;Here are the practical things you need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The most popular Agile way of working is &lt;a href="https://en.wikipedia.org/wiki/Scrum_%28software_development%29"&gt;Scrum&lt;/a&gt;. Divide your project into “sprints” of two weeks and put “tasks” in the content of the sprint. All the rest is for the future and is called the “&lt;a href="https://www.scrum.org/resources/what-is-a-product-backlog?gclid=EAIaIQobChMIrYurlr7o2AIVEhoYCh0dwQVdEAAYASAAEgIXtPD_BwE"&gt;backlog&lt;/a&gt;”. This is useful to track progress, adjust planning going forward, and improve &lt;a href="https://www.mountaingoatsoftware.com/blog/know-exactly-what-velocity-means-to-your-scrum-team"&gt;team velocity&lt;/a&gt; over time.&lt;/li&gt;
&lt;li&gt;Another Agile approach is &lt;a href="https://en.wikipedia.org/wiki/Kanban"&gt;Kanban&lt;/a&gt;. The idea is to limit the number of tasks that are “in progress”. This way, you are sure to close one item fully before moving to the next. There are no sprints or time frames like in Scrum. You just go through tasks one after the other until you’re done.
In Agile, a software project will be split into tens or hundreds of tasks. You need a tool to manage them. The reference tool is &lt;a href="https://www.atlassian.com/software/jira"&gt;JIRA&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other tools &lt;a href="https://blog.capterra.com/5-outstanding-atlassian-jira-alternatives-for-your-growing-tech-team/"&gt;exist&lt;/a&gt; of course, but you will probably have to work with JIRA at some point. So if you are new to these all the tools, just go for JIRA. There is a free 7 day trial. It is more than enough to get an overview of how things work. Again, this is fairly straightforward, and very well-documented.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Testing and Continuous Integration
&lt;/h1&gt;

&lt;p&gt;Git and agile tools allow teams to go fast. With speed inevitably come errors and bugs. Consider a team of five developers working on rather independent pieces of code. Each of them will make a pull request to the repository. Two problems can arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once the code of the “first developer” gets to the Git repository, the code of the others is no longer valid or working because some things have changed. This is not a mistake from “the first developer”, it is just life and it will happen.&lt;/li&gt;
&lt;li&gt;Once all the developers pushed their code to the Git repository, chances that everything work as expected out of the box are rather low. Again, this is not a reflection of poor teamwork. It will just happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, you need to test your software. Even after each developer has pushed their code? It would be great, but a waste of time.&lt;/p&gt;

&lt;p&gt;When other developers push their code, we’ll need to repeat this task. Should you test once everyone has pushed their code? Also great, but you’ll find bugs late in the process, which can delay the whole project. So how can you solve this?&lt;/p&gt;

&lt;p&gt;Automated tests and &lt;a href="https://fire.ci/blog/how-to-get-started-with-continuous-integration/"&gt;Continuous Integration&lt;/a&gt; are there to the rescue.&lt;/p&gt;

&lt;p&gt;Automated testing is a topic one could write many books about. Each language and framework has its own set of tools. Listing them all would not make sense here. Just keep in mind that testing is time consuming and not always planned for.&lt;/p&gt;

&lt;p&gt;Regardless, you should know how to write unit tests for your code and be proactive about writing them. If you don’t have time to actually do it, you should at least be aware that it’s wrong.&lt;/p&gt;

&lt;p&gt;Continuous Integration is a process in which every push to your repository triggers a build and runs your tests automatically. A red flag is raised as soon as a faulty commit goes in. Pull requests should also be tested automatically before merging to avoid bugs that impact the whole team.&lt;/p&gt;

&lt;p&gt;Continuous Delivery is the extension of Continuous Integration. If the tests pass properly, the tested version is pushed to the production environment automatically.&lt;/p&gt;

&lt;p&gt;As an example: at Quora, they track the time between a push to the repository and deployment of that code to production servers. The last benchmark I read about was 15 minutes … for around 100 developers. This is the most powerful setup a team could hope for.&lt;/p&gt;

&lt;p&gt;Popular continuous integration servers are Jenkins, Travis, CircleCI, and a few more. We at Fire CI promote a &lt;a href="https://fire.ci"&gt;serverless approach to Continuous Integration&lt;/a&gt; to put developers productivity at its best. You do not need to know how to set the server up and everything, but you should know such tools exist, and a red alert should trigger in your head if your team do not use any.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Patience and understanding
&lt;/h1&gt;

&lt;p&gt;As a developer your main frustration will not come from the code. You might spend a few hours searching Google and Stack Overflow for answers&lt;br&gt;
but this is a driving activity. The real pain will come from the outside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unclear or misunderstood requirements&lt;/li&gt;
&lt;li&gt;People unhappy with the end result of your work because they didn't knew what they wanted in the first place and they expected you to figure it out&lt;/li&gt;
&lt;li&gt;Tools that become slow or stop working for some reasons&lt;/li&gt;
&lt;li&gt;Interruptions now and then because people think that sitting at your desk means they can disturb you any time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The keyword here is "patience". Try to understand the people around you. They don't know everything. Remember that you don't either. Do make them understand though that your time is valuable and that they should respect it.&lt;/p&gt;

&lt;p&gt;The last but not the least when it comes to customers and requirements: do not accept low level or clumsy detailed requirements if you didn't get a brief on the big picture. You need to understand the business you are building software for. At the end of the day your code is here to improve an existing process or enable new ones.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The better your understand the business side of things, the better developer you'll be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The core of a developer’s job is coding. Once you move from solo/hobbyist to member of a performing team, proper use of the key collaboration tools is as important as writing clean code. Hopefully this article gave you an overview of what these tools are and how to dig further to be the best at what you do!&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://fire.ci/blog"&gt;Fire CI Blog&lt;/a&gt; on April 5th, 2019.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why you should not use (long-lived) feature branches</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Fri, 19 Apr 2019 10:30:50 +0000</pubDate>
      <link>https://dev.to/jpdelimat/why-you-should-not-use-feature-branches-h68</link>
      <guid>https://dev.to/jpdelimat/why-you-should-not-use-feature-branches-h68</guid>
      <description>&lt;p&gt;Isn't the git history in the picture above nice to work with? There are probably many problems in that repository, but one of them is most definitely the use of feature branches. Let's see how such a bad thing became such a common practice.&lt;/p&gt;

&lt;p&gt;When git came to replace SVN it brought a ridiculously easy way to create branches. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The idea behing git is to ease your pain as a developer working on many features at a time. Not to push branches around and tie them to the whole development process of your team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When JIRA and others came along , companies like Atlassian started to heavily promote the "git workflow" and feature branches. The "Create branch" button appeared in your JIRA tasks and boom, feature branches were born! Atlassian tells you all about it in &lt;a href="https://www.atlassian.com/agile/software-development/branching"&gt;this interesting article&lt;/a&gt;. I like Atlassian's products very much. Keep in mind though that their core business is tasks management for development teams. The more tangled it gets with branches and code, the better.&lt;/p&gt;

&lt;p&gt;Ten years later, feature branching is a standard in most teams, when in fact it doesn't bring any benefits to your bottom line: release quality software to production. Not only feature branches provide zero benefit, they actually slow you down!&lt;/p&gt;

&lt;p&gt;For the sake of clarity: this article assumes a feature branch will carry the whole feature you are developing and is a so called 'long-lived' feature branch that will last 1 week or more. It's not a "no branches at all" mantra.&lt;/p&gt;

&lt;h2&gt;
  
  
  "The feature is ready. I just need to merge it!"
&lt;/h2&gt;

&lt;p&gt;I've heard this way too many times. This falls in the same category as statements like "It compiles so it works".&lt;/p&gt;

&lt;p&gt;In practice the merging leads to the "unpredictable release syndrome". It can be quick or evince a major incompatibility, which needs fixing in a rush. You are either lucky or … your timeline shifts and code quality drops.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The real problem with feature branches is the reason they are so popular: they pump a developer's pride and make you feel good about your work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your feature branch is your own perfect garden and you can keep it clean and shiny. But it is separated from the other gardens of your team. The more you work apart, the harder it is to reconcile.&lt;/p&gt;

&lt;p&gt;I am a big fan of the management book "The goal". It shows how over time people tend to use metrics that highlight local optimums of a process because it is more comfortable. They just lose focus on their global bottom line. The book is about a production plant, but the analogy stands. Your feature branch is a local optimum with high quality code. It may also be so far off the main branch that it is of no use for the upcoming release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trunk based development to the rescue
&lt;/h2&gt;

&lt;p&gt;As the name suggests, in trunk based development the whole team pushes continuously to the main branch or use very short lived (1 or 2 days max) branches.&lt;/p&gt;

&lt;p&gt;Here is a &lt;a href="https://trunkbaseddevelopment.com/"&gt;detailed description&lt;/a&gt; of the idea. I have no affiliation with the linked website. It is just a great overview of the concept.&lt;/p&gt;

&lt;p&gt;When you push your work to the main branch often, the amount of code to merge is way smaller and it becomes trivial. There is a far greater benefit though: you and your team can spot problems before they become painful. It might be that your refactoring clashes with another feature. Or you are drifting off from the project conventions or architecture patterns. This is the real value of the process. As I preach in any place I find myself in:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is teamwork that makes or breaks software projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Working days on code that will never get to the release on time is the biggest failure there is for a team.&lt;/p&gt;

&lt;p&gt;Another upside of pushing or merging often to the main branch is that your changes will run live in some environment. It is always good to deploy and battle test your code, even in progress, in some real deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  "WIP" in the main branch?
&lt;/h2&gt;

&lt;p&gt;If you read so far you are probably thinking "This is crazy, how can I push my half done work to the main branch when it will probably get deployed to production very soon!?!".&lt;/p&gt;

&lt;p&gt;Here are the common objections one might have and a tentative solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can I expose unfinished work?
&lt;/h3&gt;

&lt;p&gt;Use feature toggles. They can be environment variables or whatever suits you best to turn on and off your work in progress. Make it defensive of course so your half finished code does not get active in production by mistake.&lt;/p&gt;

&lt;p&gt;Your whole team will love this: you can activate code on any environment at any time to see how it looks or performs. Testers can prepare testing early on. Product owners can comment on your work along the way. It is all live and easy to access for everyone! If your work is like just started this provide little value. But evil lies in the details. It usually takes half the time to get to 90% completion and another half to finish the remaining 10%. Sharing your work in this state of 90% completion is always a good idea ;)&lt;/p&gt;

&lt;p&gt;Another thing that comes for free: you can turn the feature off in production if a problem arise after deployment. After a few days or weeks, once the feature runs smoothly, just remove the toggle from the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I break the main branch for everyone?
&lt;/h3&gt;

&lt;p&gt;It is 2019. If you don't have a continuous integration setup that builds and runs tests automatically … then set it up yesterday. If you break anything you'll be notified before it becomes a problem for the whole team. In pure Trunk Based Development the feedback will come after the merge and has to be fixed right away. If you are using short lived branches the merge should be blocked by your CI tool. A short lived branch is something that should last 1 or 2 days max and carry a consistent piece of code that contribute to the feature you are building.&lt;/p&gt;

&lt;h3&gt;
  
  
  There must be a code review before we merge anything in!
&lt;/h3&gt;

&lt;p&gt;That's a valid point. Code reviews do not need feature branches though. If the code review culture is strong in your team then it can very well be done on the commit to the main branch. The reviewer would stop by the author of the commit and discuss what needs to be fixed. The fix would come in another commit. Even better, have the code review together before pushing the commit in the first place.&lt;/p&gt;

&lt;p&gt;If it is not acceptable to your team to have post merge code reviews (because let's face it it is less handy as tools do not really support that), use short lived branches and apply your code review process there.&lt;/p&gt;

&lt;h3&gt;
  
  
  I want to see the code that is related to a task
&lt;/h3&gt;

&lt;p&gt;If you have a given branch per feature then it is easy to track code back to your agile board. You can navigate from a task to the branch that implements it.&lt;/p&gt;

&lt;p&gt;It sounds cool, while in reality this is useless! How many people can you have on an agile team? Up to 5? Up to 10? How hard is it to ask the guy running a task or story what commits you need to look at to deep dive into the implementation?&lt;/p&gt;

&lt;p&gt;After some time, once tasks are completed for a long time, linking tasks to code does not make sense anymore. Developers rely on git blame to know the who, on code content to know the how, and hopefully on comments to know the why.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cherry on the cake: opt-in on new features
&lt;/h3&gt;

&lt;p&gt;It became common to see major UI features or updates released using an "opt in" approach. Github, Bitbucket, Gmail, … to name a few.&lt;/p&gt;

&lt;p&gt;The concept is that major changes are introduced using a banner "Hey we have this new feature / improved dashboard / whatever. Click here to try it out". You can opt in, and usually opt out as easily if you don't like the change. This is a very good adoption testing strategy as it involves the end users in the decision process. If people opt in and stay there it means you are improving the experience. If they opt in and out … you know you've changed things for worse.&lt;/p&gt;

&lt;p&gt;If you are using feature toggles from the start, exposing these on a per user basis at run time becomes very easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you never thought of trunk based development as an alternative to the feature branch mantra, I hope this article gave you some perspective and will to try it out. &lt;/p&gt;

&lt;p&gt;The best thing is that to get there you'll need to setup or improve every other aspect of your process (CI, automated tests, code reviews). This is a good path to take. We obviously recommend Fire CI as a &lt;a href="https://fire.ci"&gt;continuous integration tool&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Remember that your bottom line is to put quality software in front of your users. The closer your code is from the production environment at all times, the better.&lt;/p&gt;

&lt;p&gt;Now, although the article is very much "trunk based development" oriented, note that it might not be a valid approach for your team.&lt;br&gt;
If your team is highly distributed, in different time zones, has a lot of junior developers who need to learn the project conventions and architecture, using longer lived feature branches might work better.&lt;/p&gt;

&lt;p&gt;The main idea in this article is: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The faster you integrate the different pieces together and check that things are working, the safer you are to have a working product at the end. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A good common ground is to use short lived branches that last 1 or 2 days max and merge them to the main branch. This way you can human control what gets in and still integrate code fast.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://fire.ci/blog/why-you-should-not-use-feature-branches/"&gt;Fire CI Blog&lt;/a&gt; on March 30, 2019.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>devops</category>
      <category>git</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to get started with Continuous Integration</title>
      <dc:creator>Jean-Paul Delimat</dc:creator>
      <pubDate>Mon, 15 Apr 2019 09:03:55 +0000</pubDate>
      <link>https://dev.to/jpdelimat/how-to-get-started-with-continuous-integration-1o9b</link>
      <guid>https://dev.to/jpdelimat/how-to-get-started-with-continuous-integration-1o9b</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VTL0errv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/8vor9xy8genttailwmra.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VTL0errv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/8vor9xy8genttailwmra.jpg" alt="Continuous Integration in real life"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything you need to know to get started with continuous integration. Branching strategies, tests automation, tools and best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The goal: Deliver working code quickly and safely
&lt;/h2&gt;

&lt;p&gt;The goal of Continuous Integration is to deliver code to the main branch of your repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quickly: from pushing new code to the repository to merging it to the main branch when it works should be done within minutes&lt;/li&gt;
&lt;li&gt;Safely: how do we know the new code works? Continuous Integration is about setting up the right checks to merge code automatically in full confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continuous Integration is a bit about tools and a lot about mindset and culture in the team. You want your development process to facilitate fast integration of new code while keeping a working main branch at all times. This working main branch will enable Continuous Delivery or Continuous Deployment in the future. But these are for another post. Let's focus on Continuous Integration first.&lt;/p&gt;

&lt;p&gt;There are 2 pillars to achieve Continuous Integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work in small chunks
&lt;/h3&gt;

&lt;p&gt;Imagine a team of 5 working on a SaaS product. Each of them develops a separate new feature. The workload on each feature is about 1 or 2 weeks. There are 2 ways to go here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The team could go with feature branches. Each developer will work on its part on a "feature branch". The branches will be merged to the main branch once everyone is happy with their work&lt;/li&gt;
&lt;li&gt;The team could go with branches (still) but integrate their work to the main branch on every push . Even if things are still work in progress! The work in progress would remain invisible to any end user or tester of the main branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which approach do you think will work best? &lt;/p&gt;

&lt;p&gt;The first approach will ultimately lead to the "unpredictable release syndrome". Long lived feature branches create a false sense of safety and comfort for each developer &lt;strong&gt;individually&lt;/strong&gt;. As the branches drift apart for a long period of time, there is no way to measure how hard it will be to merge it all. At best some minor code conflicts will arise, at worst fundamental design assumptions will be challenged and things will have to be reworked ... the hard way. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rework will be done under time pressure, leading to a drop in quality and accumulation of technical debt. This is a vicious circle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See the post about &lt;a href="https://fire.ci/blog/why-you-should-not-use-feature-branches/"&gt;Why you should not use feature branches&lt;/a&gt; for the dirty details.&lt;/p&gt;

&lt;p&gt;The second approach is what we need to enable continuous integration. Each developer does work on its own branch. Difference is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Changes are merged to the main branch on every push, and each developer syncs its branch with latest main branch version a few times a day. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This way the team can fix conflicts and align design assumptions faster and easier. 5 small problems discovered early on are way better that 1 big problem discovered just before release day. Check out the "Feature Toggles" section below to see how you should integrate "work in progress" to the main branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safety comes with automated checks
&lt;/h3&gt;

&lt;p&gt;Ancient software development process was based on a build cycle followed by a test cycle. And this would probably still fit the "feature branches" approach described above. If we integrate and merge code tens of times a day, manual testing does not make sense. It would take too long. We need automated checks to verify that the code works properly. We need a CI tool that will take each developers' push and run build and tests automatically.&lt;/p&gt;

&lt;p&gt;The tests type and content should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast enough to provide feedback to the developer within  minutes&lt;/li&gt;
&lt;li&gt;thorough enough to merge the code to the main branch in full confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately there is no one size fit all tests type and content. The right balance is specific to your project. Do not run  large and time consuming test suites during your CI phase. Such tests provide better safety but they come at the cost of a delayed feedback to the developers. This leads to context switching which is a pure waste of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize developers time and reduce context switching
&lt;/h2&gt;

&lt;p&gt;Long CI checks, and by long I mean over 3 minutes, introduce a compound waste of time for each developer in your team.&lt;br&gt;
Let's compare a "good" and a "bad" worklow. The "good" workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You commit and push your code&lt;/li&gt;
&lt;li&gt;The CI build and tests run for 1 to 3 minutes&lt;/li&gt;
&lt;li&gt;During the 1 to 3 minutes you review the task at hand, bump the status in some management tool, or review your code once again&lt;/li&gt;
&lt;li&gt;Within 3 minutes you get a succesfull status: you can move on to the next part of your task. If you get a failed build: you can fix the issue right away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "bad" workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You commit and push your code&lt;/li&gt;
&lt;li&gt;The CI build and tests run for 15 minutes&lt;/li&gt;
&lt;li&gt;What do you do during these 15 minutes?

&lt;ul&gt;
&lt;li&gt;You could grab a cup of coffee with the team. Fair enough, but how many of these can you have per day?&lt;/li&gt;
&lt;li&gt;You would probably get your head on the next task in your pipeline&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;15 minutes later you get a failed build notification. You need to switch back to the previous task, try to fix the issue ... and go for another 15 minutes loop ...&lt;/li&gt;
&lt;li&gt;At that point you are wondering: should I get back to this next task again or just wait the 15 minutes and achieve peace of mind that I am actually really done with my current task ...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bad workflow is not only a waste of time. It is also frustrating for developers. And productive developers are happy developers. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need to align your tools and workflows to keep your developers happy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Branching
&lt;/h3&gt;

&lt;p&gt;Continuous Integration is about integrating code from different developers branches to a common branch in your configuration management system. Chances are you are using git. In git the default main branch in a repository is called "master". Some teams create a branch called "develop" as the main branch for continuous integration. They use "master" to track deliveries and deployments (develop being merged to master).&lt;/p&gt;

&lt;p&gt;You probably already have a main branch your team pushes or merges code to. Stick with it.&lt;/p&gt;

&lt;p&gt;Each developer should work on their own branch. One can use multiple branches if working on many different topics at once. Although this would be a sign of "unfocused" work at best.&lt;br&gt;
As soon as a consistent part of your code is ready, push your repository. The CI will checks will kick in and merge your code to the main branch if they are successful. If the checks fail, you are still on your own branch and can fix whatever needs to be and push again.&lt;/p&gt;

&lt;p&gt;The important word in the process above is &lt;strong&gt;consistent part of your code&lt;/strong&gt;. How do you know it is consistent? Simple. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you can easily come up with a good commit message, it's consistent. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the other hand if your commit message needs 3 bullets and many adjectives and adverbs, it's probably not good. Split your work in multiple, consistent commits. And then push the code.&lt;br&gt;
Consistent commits help code reviews and make the repository history easier to follow.&lt;/p&gt;

&lt;p&gt;Don't just push whatever because it's the end of the day! &lt;/p&gt;

&lt;h3&gt;
  
  
  Pull requests
&lt;/h3&gt;

&lt;p&gt;What is a pull request? A pull request is a concept in which you ask the team to merge your branch to the main branch. The acceptance of your request should go through a status provided by your CI tool and potentially a code review. Ultimately the manual acceptance of a human being in charge of merging the pull requests.&lt;/p&gt;

&lt;p&gt;Pull requests were born in open source projects. Maintainers needed a structured way to evaluate contributions before merging them in. Pull requests are not part of Git. They are supported by any Git provider though (GitHub, BitBucket, GitLab, ...).&lt;/p&gt;

&lt;p&gt;Note that pull requests are not mandatory for Continuous Integration. Their main benefit is to support a code review process, which cannot be automated by design.&lt;/p&gt;

&lt;p&gt;If you are using pull requests, the same principles or "work in small chunks" and "optimize developers time" apply: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep each pull request small and with one clear purpose (it will make code review way easier)&lt;/li&gt;
&lt;li&gt;keep your CI checks fast&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Automated checks
&lt;/h3&gt;

&lt;p&gt;The heart of your Continuous Process are automated checks. They ensure that the main branch code is working properly after your code is merged. If they fail, your code does not get merged. As a minimum the code should compile or transpile or whatever is your tech stack doing to make it ready for runtime. &lt;/p&gt;

&lt;p&gt;On top of compilation you should run automated tests to ensure the software works properly. The better the test coverage, the better confidence you can have in the new code being merged to the main branch. Careful though! Better coverage means more tests and longer execution time. You need to find the right tradeoff.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Where do you start when you have no tests at all or need to cut down on some long running tests? Focus on what is critical for your project or product. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are building a SaaS application, you should check that users can sign up or login, and perform the most basic operations your SaaS provides. Unless you are developing a Salesforce competitor, you should be able to run your tests within minutes if not seconds. If you are building a heavy data processing backend: use limited data sets to exercise the different building blocks. Keep long running runs on large data sets out of Continuous Integration. Long running tests can be triggered after code is merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Feature toggles
&lt;/h3&gt;

&lt;p&gt;The key concept of Continuous Integration is to put your code in the main branch as soon as possible. Even work in progress. Even features that do not fully work or that you don't want exposed to testers or end users. The way to achieve that is to use feature toggles. Have your new feature under an enabled/disabled toggle. The toggle can be a compile time boolean flag, an environment variable or a runtime thing. The right approach depends on what you want to achieve.&lt;/p&gt;

&lt;p&gt;The first major benefits of feature toggles is that you can take them up to production and enable/disable the new feature upon need. You could restart a server with a changed environment variable, or toggle on/off a new UI dashboard layout. This way you have full flexibility to roll out the feature. Or disable it if causes unexpected issues in production. Or allow end users to opt in or out of the feature (in case of the UI toggles).&lt;/p&gt;

&lt;p&gt;The second major benefit of feature toggles is that they force you to think of the boundary between what you are doing and the existing code. This is a good exercise and this is what you should start with anyway every time you make an addition to an existing system. The feature toggle step makes this step of the process more visible.&lt;/p&gt;

&lt;p&gt;The only drawback on feature toggles is that you need to clean them up periodically from the environment and from the code. Once a feature is battle tested and adopted by users, it should be the default. The code for the toggle and the old version of things (if any) should be cleaned up. Don't fall into the trap of a "configuration as toggles" system. The pitfall is that you will never be able to maintain and test all combination of the toggles and have a fragile architecture in the end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep CI build time under 3 minutes
&lt;/h3&gt;

&lt;p&gt;Remember the "good" and the "bad" workflow in the first part of the article. We want to avoid context switching for developers. Pick your phone and set a 3 minutes timer on. See how long it is when you are just waiting for something! 3 minutes should be an absolute maximum for  you to focus and move safely and efficiently from one task to the other.&lt;/p&gt;

&lt;p&gt;A build under 3 minutes might seem crazy to some teams, but it is definitely achievable. It has more to do with how you organize your work than the tools you use. Ways to optimize your build are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use more build capacity: if you don't have enough concurrent builds on your CI tool and builds get queued, developers lose time&lt;/li&gt;
&lt;li&gt;Leverage caching: most tech stacks require to install and configure dependencies when running a fresh build. Your CI tool should be able to cache these steps when dependencies do not change to optimize build time&lt;/li&gt;
&lt;li&gt;Review your tests: check that your tests are optimized for time. Remove timeouts and "safely long" waiting steps. If you have heavy tests suites to run, consider moving them on a separate build that is run after the merge to the main branch. They would not be part of the Continuous Integration safeguard anymore, but heavy tests should not be anyway&lt;/li&gt;
&lt;li&gt;Split your code base: do you have to have everything in one repository? do  you have to build and run tests on everything even when some small part changes? There might be wins here. &lt;/li&gt;
&lt;li&gt;Run tests conditionally: run your tests only if some directories have changed. If your codebase is well organized, this can be a huge win&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The great thing about forcing a short time limit on your CI checks is that it requires you to fundamentally improve your whole development process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As &lt;a href="https://www.jimrohn.com/"&gt;Jim Rohn&lt;/a&gt; said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Become a millionaire not for the million dollars, but for what it will make of you to achieve it"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The virtual merge: your code alone does not really matters
&lt;/h3&gt;

&lt;p&gt;Most Continuous Integration tools run the CI build on your branch to say if it can be merged or not. But that is not what is of interest here. If you know what you're doing there is a pretty good chance that the code you have just pushed is working already! What your CI tool is supposed to verify is that your branch merged with the main branch works properly.&lt;/p&gt;

&lt;p&gt;Your CI tool should perform a local merge of your branch to the main branch and run the build and tests against that. Your branch can then be automatically merged if the main branch does not change in the meantime. If it does change, the CI checks should be run again until your code can be safely merged. If your CI tools does not support this kind of workflow, change your tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  The evil task manager
&lt;/h3&gt;

&lt;p&gt;There is a misbelief that it is cool to be able to trace the code related to a task in your Agile board or bug tracker like JIRA or similar. While it is a nice concept on paper, the impact on the development process is for sure not worth the effort. The task manager provides a "features and bugs" view of the world. The code is structured and layered in a very different way. Trying to reconciling an item in the task manager and a set of commits is pointless. If you want to know why a piece of code has been written, you should be able to get the information from the code context and comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Tools are only tools. Setting up the tools is probably a 1 hour thing. If you use them wrong though, you won't get the expected results. &lt;/p&gt;

&lt;p&gt;Keep in mind the goals we set for ourselves for Continuous Integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deliver working code quickly and safely&lt;/li&gt;
&lt;li&gt;Optimize developers time and reduce context switching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real deal is about shifting your mindset to "continuously deliver value" to your project or product. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Think of your software development process as a hardware production facility. Developers code represent the moving parts. The main branch is the assembled product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The faster you integrate the different pieces together and check that things are working, the safer you are to have a working product at the end. &lt;/p&gt;

&lt;p&gt;A few practical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are working on a new feature and have to change a low level component others will most likely use. Make a dedicated commit for that common component part and have it merge already. Then continue working on the rest of your feature. Other developers will be able to base their work on your change right away.&lt;/li&gt;
&lt;li&gt; You are working on a large feature that will require a lot of time and code? Use a feature toggle. Don't work in isolation. Ever!&lt;/li&gt;
&lt;li&gt; You are waiting for a code review but no one is available to do it. If your code is passing the CI checks then just merge it and make the code review happen afterwards. If it sounds like breaking the process, remember that "done is better than perfect". If it is working, it provides more value in the main branch than parked on the side for days.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Article originally published at &lt;a href="https://fire.ci/blog"&gt;fire.ci&lt;/a&gt; on April 9, 2019.&lt;/p&gt;

</description>
      <category>ci</category>
      <category>devops</category>
      <category>git</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
