<?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: Elliot Nelson</title>
    <description>The latest articles on DEV Community by Elliot Nelson (@7tonshark).</description>
    <link>https://dev.to/7tonshark</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%2F600273%2F7671d929-2fa7-4735-98f7-daad873cb4b3.jpg</url>
      <title>DEV Community: Elliot Nelson</title>
      <link>https://dev.to/7tonshark</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/7tonshark"/>
    <language>en</language>
    <item>
      <title>True Scalability &amp; Responsibility with AI</title>
      <dc:creator>Elliot Nelson</dc:creator>
      <pubDate>Tue, 29 Jul 2025 18:30:00 +0000</pubDate>
      <link>https://dev.to/7tonshark/true-scalability-responsibility-with-ai-4ijc</link>
      <guid>https://dev.to/7tonshark/true-scalability-responsibility-with-ai-4ijc</guid>
      <description>&lt;p&gt;I was reading a blog post recently about "scaling up your productivity with AI" (I'm sure you've seen many); for whatever reason, this time the phrase really struck me. I'm increasingly convinced that AI, as we currently use it, does not truly "scale" developers. This isn't a tech problem: no matter how good the AI model becomes, it's impossible for 1 human + 1 AI to be equivalent to 2 humans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegation: Tasks vs. Responsibility
&lt;/h2&gt;

&lt;p&gt;To truly scale, it's not enough to just delegate &lt;em&gt;tasks&lt;/em&gt;, you also need to delegate &lt;em&gt;responsibility&lt;/em&gt; for those tasks.&lt;/p&gt;

&lt;p&gt;Imagine a manager who runs a small development team of six people. This manager makes commitments to their customers -- perhaps "we aim for less than X defects a month" or "we address P0 bugs within 48 hours". These commitments represent the manager's responsibilities. Similarly, the individual contributors have their own lower-level responsibilities: follow best practices, run appropriate test plans, peer review changes before they merge, and so on.&lt;/p&gt;

&lt;p&gt;But what if the manager, instead of delegating the lower-level responsibilities to the engineers, kept them all for themselves? Even with six people it would immediately be overwhelming: to be &lt;em&gt;personally&lt;/em&gt; responsible for reviewing every pull request, ensuring requirements had been followed, and so on.&lt;/p&gt;

&lt;p&gt;When a developer uses AI assistants, they are often able to move &lt;em&gt;faster&lt;/em&gt; -- maybe twice as fast, or five times as fast, for some particular task. But they have not &lt;em&gt;scaled&lt;/em&gt;, because they retain all of the responsibility for all of the work they and their AI assistant have produced, and this responsibility only grows as the amount of work grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegation &amp;amp; Augmentation
&lt;/h2&gt;

&lt;p&gt;I've seen a number of articles now about the shift from AI as a "tool" (increasing personal productivity for developers) to "teammate" (taking a more fundamental role within the team). In other words, AI isn't just augmenting your developer's productivity, we can actually delegate tasks to them. AI technology is moving fast, but right now, this idea feels like nonsense to me.&lt;/p&gt;

&lt;p&gt;Do you have a &lt;em&gt;manager in your organization&lt;/em&gt; that feels comfortable &lt;em&gt;taking personal responsibility&lt;/em&gt; for the decisions their "AI employees" make? If not, no actual delegation is taking place, and AI is only augmenting the abilities of the people the manager can &lt;em&gt;actually&lt;/em&gt; delegate to (their developers).&lt;/p&gt;

&lt;p&gt;Today, an "AI teammate" is just a fig leaf for increased scope of ownership for the human teammates, along with everything that entails (architectural knowledge, on-call shifts, code reviews, and so on). Claiming you can treat an AI like it is a coworker might be fun -- until something happens that requires true accountability and your imaginary friend is nowhere to be found.&lt;/p&gt;

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

&lt;p&gt;There's a hard cap to the cognitive burden humans can carry, and the amount of output you are responsible for directly increases that burden. You can probably increase a developer's speed by 10x using AI, at least temporarily. However, "10x'ing" the responsibility of a developer over the long term is going to result in engineer burnout (or silent failures).&lt;/p&gt;

&lt;p&gt;As we continue to integrate AI into our tools and processes, we need to be mindful of the difference between acceleration and true scalability, or risk making organizational changes that aren't sustainable.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Striving for Asynchronous Work</title>
      <dc:creator>Elliot Nelson</dc:creator>
      <pubDate>Thu, 29 Apr 2021 20:00:53 +0000</pubDate>
      <link>https://dev.to/7tonshark/striving-for-asynchronous-work-23f2</link>
      <guid>https://dev.to/7tonshark/striving-for-asynchronous-work-23f2</guid>
      <description>&lt;p&gt;Scroll through your recent Slack history and look for conversations that begin like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Who do I ask to spin up a new client pipeline?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"I have a new developer on my team, how do they get access to the VPN?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"How do I create a new team in JIRA?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Where is that documentation for upgrading node on a service?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"I have a question about [THING], what team owns that?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if your company is full of friendly, helpful people, and all of these questions get resolved a few minutes later, these questions should ring warning bells. Each of these questions is turning an &lt;em&gt;inherently asynchronous&lt;/em&gt; process into a &lt;em&gt;synchronous&lt;/em&gt; one -- someone that was actively working on a task (onboarding a new software engineer, creating a new pipeline, etc.) has stopped working on that task to ask a question, and likely won't be able to continue again until someone appears to answer it.&lt;/p&gt;

&lt;p&gt;In a small company with just a few teams, where everyone is in the same time zone, those few minutes of extra waiting don't seem like a big deal. But as a company grows, both in team size and in number of time zones, the number of questions &lt;em&gt;and&lt;/em&gt; the amount of time waited can explode. In a company that has become remote, that spans 3, 4, maybe even 10 time zones, stopping and asking a question on Slack isn't just a few minute wait; it often means that task is now blocked until the next day.&lt;/p&gt;

&lt;p&gt;If your company is full of synchronous workflows, a software engineer's day quickly becomes an exhausting series of task-switching exercises, pushing a little bit further on each of their many blocked tasks, rarely being able to pursue the thread of a single task to completion. It's hard to be happy as a developer when you end the day feeling like you haven't accomplished something -- it will kill your productivity just as quickly as a slow CI build or broken bug workflow will.&lt;/p&gt;

&lt;p&gt;If you recognize some of the questions above in your own company's chat log, prioritize fixing it! What needs to be fixed depends on what kind of questions are being asked and &lt;em&gt;why&lt;/em&gt; people are blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Solutions
&lt;/h3&gt;

&lt;p&gt;You've always intended to create an automated script, or service, or Jenkins job to do this task for developers, but in the meantime, it's easier to tell people "just slack Bob the name of your new service and he'll set it up for you". This kind of issue often &lt;em&gt;naturally&lt;/em&gt; gets resolved technically, because Bob gets sick of doing it manually, but maybe it's time for the team to prioritize that automated workflow, or CI pipeline generator, or whatever other tools your company needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;You automated all your stuff years ago, but people are still blocked in Slack just because they don't know it exists. Make sure the services you offer your software engineers are documented, and that links to that documentation exist in a place that makes sense. Ask people where they looked, or what they searched for, and bridge those gaps for next time. Build automated replies in Slack if you have to! Do whatever you can to connect people with the information they need, without waiting for another human to show up and help them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consensus Building
&lt;/h3&gt;

&lt;p&gt;This is a special case that often happens to engineers new to a company -- you need to write a script, or build a tool, or add a feature to a product, so you ask a seemingly innocuous question. &lt;em&gt;"I see repos with both Mocha and Jest, which do people prefer here?"&lt;/em&gt;, or, &lt;em&gt;"The generator says I can use Terraform or CloudFormation, is there a preference?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instead of an answer, you find yourself in the middle of a heated debate, with conflicting responses from people more senior than yourself. In this case it's blocking you from even getting started on your task, but it might block you in the middle too -- for example, you put up a PR with code changes recommended by one team lead, only to be blocked by a different team lead demanding a different approach.&lt;/p&gt;

&lt;p&gt;This issue is common on rapidly growing teams and you should consider it &lt;em&gt;especially alarming&lt;/em&gt;, as it has the potential to be exponentially frustrating and tense for engineers caught in the crossfire. Take the people with strong opinions and get them into a room or a meeting to hash it out, or task a key decision maker with gathering opinions and making a decision; however you accomplish it, it's critical to prevent these kinds of situations from becoming the norm at your company.&lt;/p&gt;

&lt;h3&gt;
  
  
  And more...
&lt;/h3&gt;

&lt;p&gt;Here's a thought exercise I like: imagine you wake up at 4am and can't sleep, so you decide to get a few hours of work done early. Look at your current work backlog -- what tasks could you do, alone, right now? What tasks need to wait until one (or several) of your coworkers are online?&lt;/p&gt;

&lt;p&gt;Some tasks &lt;em&gt;do&lt;/em&gt; require "synchronous communication", like sitting down with team members to brainstorm or chatting about a proposed feature. But for those that aren't inherently synchronous, what would be required to make it asynchronous? Is it a technical solution, is it missing documentation? Whatever it is, chipping away at those edges in your company will slowly and surely reduce the number of times your developers end up blocked each day, increasing their happiness and their productivity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@jontyson?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Jon Tyson&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/clocks?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>development</category>
      <category>productivity</category>
      <category>remote</category>
    </item>
    <item>
      <title>Unit testing async functions</title>
      <dc:creator>Elliot Nelson</dc:creator>
      <pubDate>Tue, 30 Mar 2021 20:07:39 +0000</pubDate>
      <link>https://dev.to/7tonshark/unit-testing-async-functions-cp5</link>
      <guid>https://dev.to/7tonshark/unit-testing-async-functions-cp5</guid>
      <description>&lt;p&gt;If you're just getting comfortable writing &lt;code&gt;async&lt;/code&gt; functions in JavaScript (or using Promises in general), a potential stumbling block you might run into is writing proper unit tests for them.&lt;/p&gt;

&lt;p&gt;The good news is that as long as your test framework provides a way to write expectations for &lt;em&gt;resolved values&lt;/em&gt; and &lt;em&gt;rejected values&lt;/em&gt; (usually Errors), adjusting your unit tests should be relatively simple. To give some examples, I'll show some simple positive and negative unit tests for async functions using three popular test frameworks - &lt;em&gt;Jasmine&lt;/em&gt;, &lt;em&gt;Jest&lt;/em&gt;, and &lt;em&gt;Mocha&lt;/em&gt; + &lt;em&gt;Chai&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code under test
&lt;/h3&gt;

&lt;p&gt;Before we start testing, we need an example of an asynchronous function to test, right? Let's check if a string is a palindrome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&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;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not a string`&lt;/span&gt;&lt;span class="p"&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;chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;chars&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;(This function doesn't have to be asynchronous, but let's consider it a stand-in -- perhaps our real palindrome checker is on a server and the &lt;code&gt;palindrome()&lt;/code&gt; function actually makes a REST call, etc.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Jasmine
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/jasmine/jasmine" rel="noopener noreferrer"&gt;Jasmine&lt;/a&gt; has been around a long time and remains one of my favorite test frameworks -- it's tiny, fast, and has no dependencies. It comes out of the box with async matchers, although you need to remember that &lt;em&gt;asynchronous expectations&lt;/em&gt; in Jasmine must be made using the special &lt;code&gt;expectAsync&lt;/code&gt; function instead of the usual &lt;code&gt;expect&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;palindrome&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns true if the string is a palindrome&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// You can await for value, then do a normal expect&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Madam, I'm Adam`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Or, you can do an asynchronous expectation&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Madam, I'm Adam`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeResolvedTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raises an error if the value is not a string&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeRejectedWithError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+ is not a string/&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;For positive expectations, I prefer &lt;em&gt;awaiting for a value first&lt;/em&gt; and then using a standard expect -- this is more flexible, because you can use any Jasmine matcher (like &lt;code&gt;toBeInstanceOf&lt;/code&gt;, &lt;code&gt;toContain&lt;/code&gt;, etc.). If you use the asynchronous expect, you can only make an equality comparison.&lt;/p&gt;

&lt;p&gt;For negative expectations, you don't have the option of waiting for a value (the rejected promise would fail the test). In this example I've used a regular expression, but we can also pass a string or an Error object (the API for &lt;code&gt;.toBeRejectedWithError&lt;/code&gt; is consistent with Jamine's &lt;code&gt;.toThrowError&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Jest
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/facebook/jest" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; is the opposite of Jasmine, with its huge install footprint and slower runtime, but is immensely popular nowadays (especially for React testing). Like Jasmine, Jest comes with async matchers out of the box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;palindrome&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns true if the string is a palindrome&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// You can await for value, then do a normal expect&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Step on no pets`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Or, you can do an asynchronous expectation&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Step on no pets`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raises an error if the value is not a string&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+ is not a string/&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;Notice how in Jest, you can &lt;code&gt;await expect&lt;/code&gt; for asynchronous expectations (there's not a separate function), and instead of using separate matchers, you can use the chaining functions &lt;code&gt;.resolves&lt;/code&gt; or &lt;code&gt;.rejects&lt;/code&gt; to "unwrap" a Promise and then use a normal expectation matcher. I think this is one of the better matching APIs out there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocha + Chai
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/mochajs/mocha" rel="noopener noreferrer"&gt;Mocha&lt;/a&gt; is a popular test framework that &lt;em&gt;doesn't&lt;/em&gt; bundle its own assert/expect library, which makes it very flexible but also requires installing a few more packages to setup your test environment.&lt;/p&gt;

&lt;p&gt;For this example, I am using Mocha, plus &lt;a href="https://github.com/chaijs/chai" rel="noopener noreferrer"&gt;Chai&lt;/a&gt; for its BDD &lt;code&gt;expect&lt;/code&gt; syntax and the &lt;a href="https://github.com/domenic/chai-as-promised" rel="noopener noreferrer"&gt;chai-as-promised&lt;/a&gt; plugin for asynchronous matchers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;palindrome&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns true if the string is a palindrome&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// You can await for value, then do a normal expect&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Never odd or even`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Or, you can do an asynchronous expectation&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Never odd or even`&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventually&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raises an error if the value is not a string&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;palindrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&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="nf"&gt;rejectedWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/.+ is not a string/&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;For positive expectations, the chai-as-promised library gives you the &lt;code&gt;.eventually&lt;/code&gt; chain, which works just like Jest's &lt;code&gt;.resolves&lt;/code&gt; chain and allows you to append any other regular matcher. For negative expectations, it works more like Jasmine -- there's a special &lt;code&gt;rejectedWith&lt;/code&gt; matcher. Just like the other two frameworks, you can pass an Error object, a string, or a regular expression.&lt;/p&gt;

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

&lt;p&gt;Of the three test frameworks above, I think Jest has the best, most consistent style for writing asynchronous expectations. Personally, I'll drop back to Mocha or Jasmine for small tools and libraries, because I like the smaller footprint, but all 3 frameworks are quite close -- the same &lt;em&gt;functionality&lt;/em&gt; and &lt;em&gt;testing patterns&lt;/em&gt; are available in all, and your choice boils down to which particular flavor of syntax sugar you prefer.&lt;/p&gt;

&lt;p&gt;Is there a test runner or framework you prefer (maybe one not mentioned above)? Let me know!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@franki?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Franki Chamaki&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/unit-tests?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Repeating Yourself Is OK (especially in tests)</title>
      <dc:creator>Elliot Nelson</dc:creator>
      <pubDate>Mon, 22 Mar 2021 20:45:53 +0000</pubDate>
      <link>https://dev.to/7tonshark/repeating-yourself-is-ok-especially-in-tests-2b28</link>
      <guid>https://dev.to/7tonshark/repeating-yourself-is-ok-especially-in-tests-2b28</guid>
      <description>&lt;p&gt;When writing unit tests in languages like Ruby and JavaScript, a well-tested function often has more (maybe even 2-3x more) total lines of unit tests than it does code.&lt;/p&gt;

&lt;p&gt;Because we have all this unit test code, when we notice lines that are &lt;em&gt;repeated many times&lt;/em&gt;, it can be tempting to pull all those lines out and create helper functions or test wrappers. However, this desire to avoid repetition can easily lead us to the &lt;a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction" rel="noopener noreferrer"&gt;Wrong Abstraction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A bad abstraction is a problem anywhere, but in unit tests it is &lt;em&gt;especially bad&lt;/em&gt;, because that slick little helper function -- the one that over time has grown to take 5 arguments and contains several nested if statements -- doesn't get tested itself. When the code intended to &lt;em&gt;test the code&lt;/em&gt; has its own logic (&lt;code&gt;if&lt;/code&gt; statements, &lt;code&gt;switch/case&lt;/code&gt; blocks, etc.), it's impossible to tell whether it's testing the code-under-test properly.&lt;/p&gt;

&lt;p&gt;What's important is that "DRY" (Don't Repeat Yourself) is not an end goal in itself, it is only one tool to use in the pursuit of code that is readable and maintainable. If DRYing your code actually makes it harder to read and maintain, then it isn't serving its purpose.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This example is in Jasmine; feel free to imagine it in&lt;/span&gt;
&lt;span class="c1"&gt;// Jest, Mocha, RSpec, Cucumber, etc.&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restartServer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resets keepalive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&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;stop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveWith&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;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveWith&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;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restartServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&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;stop&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&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;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keepalive&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&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="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;In my opinion, even if you have 20 more unit tests just like this one that test various different aspects of server restart behavior, it's usually &lt;em&gt;not worth it&lt;/em&gt; to try and DRY up those stub lines.&lt;/p&gt;

&lt;p&gt;To some of you this might seem to fly in the face of normal coding practice, but, let's look at some common situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A unit test unexpectedly fails. The developer goes to the test in question -- everything this test needs to run is on the screen in front them. No jumping around different code files or functions, it's all right there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Someone adds a line of code to the function and they need a new unit test. Copy and paste an existing test, make some tweaks, and the task is done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Someone makes a logic change to the function, causing the &lt;code&gt;stop()&lt;/code&gt; call to be skipped in some cases. Half of the unit tests break, the rest continue working, and the developer fixes the unit tests that broke by fixing those expectations. (What doesn't happen? The typical &lt;em&gt;Wrong Abstraction&lt;/em&gt; pattern: adding yet another boolean argument to the Big Helper Function and making yet another expectation conditional...)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My stance is that almost always, attempting to "DRY" unit test code comes with significant &lt;em&gt;disadvantages&lt;/em&gt; to readability and maintainability -- almost in reverse proportion to the usual &lt;em&gt;advantages&lt;/em&gt; it gives you in your code-under-test.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image by &lt;a href="https://unsplash.com/@ballonandon" rel="noopener noreferrer"&gt;Ben Allan&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
