<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0" >
  <channel>
    <title>Blogs on Dillon Shook</title>
    <description>Latest posts from Dillon Shook</description>
    <link>https://dillonshook.com/blog/</link>
    <image>
      <url>https://dillonshook.com/favicon.png</url>
      <title>Dillon Shook</title>
      <link>https://dillonshook.com/blog/</link>
    </image>
    <language>en-us</language>
    <copyright>All content copyright Dillon Shook</copyright>
    <lastBuildDate>Wed, 17 May 2023 07:37:42 +0000</lastBuildDate><atom:link href="https://dillonshook.com/blog/rss.xml" rel="self" type="application/rss+xml" /><ttl>120</ttl>
    
    
    <item>
      <title>Tradeoffs in Testing</title>
      <description>I&amp;rsquo;m finally getting around to putting down in words the testing philosophy I&amp;rsquo;ve developed over the years. Some of it might rock the boat a bit, other parts may be obvious to some folks, but what I&amp;rsquo;d like you most to take away is a reflection on your own testing philosophy and the tradeoffs your making. There&amp;rsquo;s no free lunch.
Choosing how much time to invest in testing is all about balancing how confidently you can say your program is doing what you intend to and how fast you can modify or add to your program.</description>
      <link>https://dillonshook.com/tradeoffs-in-testing/</link>
      <pubDate>Wed, 17 May 2023 07:37:42 +0000</pubDate>
      
      <guid>https://dillonshook.com/tradeoffs-in-testing/</guid>
      
      <content:encoded><![CDATA[<p>I&rsquo;m finally getting around to putting down in words the testing philosophy I&rsquo;ve developed over the years.  Some of it might rock the boat a bit, other parts may be obvious to some folks, but what I&rsquo;d like you most to take away is a reflection on your own testing philosophy and the tradeoffs your making.  There&rsquo;s no free lunch.</p>
<p>Choosing how much time to invest in testing is all about balancing how confidently you can say your program is doing what you intend to and how fast you can modify or add to your program.  Writing tests takes time that could otherwise be spent adding new features.  When requirements change in a project with tons of tests, tests will break and that means spending time debugging and evaluating what should change in the tests as well.  The reason to write tests though of course is to save time from manually verifying everything still works after changing the program.</p>
<p>So the testing approach that strikes the right balance of value for time spent is extremely dependent on the type of project you&rsquo;re working on.  It really irks me to read articles on testing that bake in lots of assumptions about the project, team size, product needs, etc.  Their conclusions could be totally wrong for your situation!  The obvious example is that you need way more tests for an MRI machine than for your weekend web app side project.</p>
<p>In general I think these are the four big areas that determine what your test coverage should look like.</p>
<style>
  .diagram-row{
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
  }
  .diagram-row > img{
    min-width: 195px;
  }
</style>
<div class="diagram-row">
  <img src="./team-size.svg" />
  <img src="./maturity.svg" />
  <img src="./dependent-code.svg" />
  <img src="./cost-of-failure.svg" />
</div>
<p>Lets break down each one:</p>
<p>When the <strong>team size</strong> is small each developer knows a greater percentage of the codebase and is less likely to inadvertently break something because they didn&rsquo;t know about it. As the team size increases, developers tend to focus on smaller parts of the codebase so having increased test coverage can catch bugs when cross cutting changes are made that affect parts of the system that the author has no awareness of.</p>
<p><strong>Product maturity</strong> is all about how big your product is and how often the requirements are changing.  In a mature product the requirements and tests will be changing less frequently, therefore having a longer time that they can catch issues &ldquo;for free&rdquo;, therefore better value for the time it took to make them.  They also help everyone remember all the different features of the product that are buried away and all the people that built them have since left.  As the product becomes so mature that it&rsquo;s put into maintenance mode, you hit a point of diminishing returns where adding tests becomes pointless since nothing new is changing to break it.  On the opposite side of the spectrum when you&rsquo;re working on a new greenfield project, requirements are being figured out and are changing constantly, lots of code is getting thrown away, and the tests you write will soon become obsolete so it&rsquo;s also not a great time investment.</p>
<p>When you&rsquo;re writing a package, library, or something else that&rsquo;s low in the stack you&rsquo;re going to have a lot of <strong>Dependent code</strong>.  When you use other peoples code yourself and it breaks you get sad.  Don&rsquo;t make people sad by not testing your code that other people depend on.</p>
<p><strong>Cost of failure</strong> is the big one here that I think warrants an exponential growth in the test coverage.  This is the heart of why the MRI machine in the previous example needs way more tests than the side project. The cost of failure in the side project is just your own time, in the MRI it&rsquo;s potentially someones life and a multi-million dollar machine.  The cost of failure is somewhat intertwined in the other metrics but there are large products with lots of people working on them that still have a way lower cost of failure than something like a mars rover.</p>
<p>So how do all these factors combine together? My mental model is to just average them all together. So for example when I built <a href="/what-it-takes-to-make-a-game-by-yourself">Macrocosm</a> it was a solo project where requirements were changing constantly (low product maturity), there was no dependent code, and a low cost of failure.  All these together tell me that very low test coverage is appropriate, and that&rsquo;s what I had.  Could I have caught bugs with more tests? Yes.  Would it have been worth it for the amount of time it would have taken to write and maintain them? No.</p>
<p>Another arbitrary example might be if you&rsquo;re on a small team, working on a legacy product, with little to no dependent code but that still brings in a ton of money for the business.  You&rsquo;ll have to pick the arbitrary numbers on the scale for yourself, but to me this warrants a moderately high amount of testing mainly driven by the cost of failure factor, but tempered by the small team and no dependencies.</p>
<hr>
<p>With a rough idea of how much to test, lets talk about some goals for actually writing tests</p>
<h2 id="testing-goals">Testing goals</h2>
<p>The overarching goal of your test suite should be to build your testing suit of armor piece by piece where each test only overlaps with other tests to ensure there aren&rsquo;t any gaps.  You don&rsquo;t want each of your tests asserting the same thing over and over (like testing authentication for each route in a web api).  That just adds bulk and slows you down when requirements change.</p>
<p><img src="./knight.webp" alt="Knight in testing armor"></p>
<p>Your knight starts out with no testing armor.  They&rsquo;re quick on their feet and can dance around other fully clad knights, but they&rsquo;re vulnerable.  When it comes time to add some protection think about what&rsquo;s most vulnerable and focus on that first.  What&rsquo;s the most vulnerable part of your application?  Really stop and think about what the most likely scenarios are for bugs and figure out how to write a test to catch them.  If the scenarios are improbable think twice about if you really need a test.  For example, I&rsquo;ve come across a number of tests for simple React components where all they do is mount the component and assert it rendered.  There wasn&rsquo;t anything complicated the components were doing that was a likely scenario to break, so we decided to shed some weight.</p>
<p>For this reason I think code coverage percentage is generally a dumb metric to strive for in all but the highest testing need projects.  What&rsquo;s the value in testing all the code in your project that is so straightforward that the only way it could break is if it was intentionally changed for new requirements?  Ideally you&rsquo;re striving for the first part of the mantra &ldquo;Either your code is so simple that there are obviously no bugs, or your code is so complex that there are no obvious bugs&rdquo;.  From what I&rsquo;ve seen in projects with arbitrary test coverage goals, people spend more time gaming the coverage metric than the time saved coming up with useful tests that catch bugs.</p>
<p>Back to the armor, your tests should also come in layers.  Under the plate armor there&rsquo;s a padded <a href="https://en.wikipedia.org/wiki/Gambeson">gambeson</a> which provides more protection and softens blows at a fraction of the weight.  By architecting your code in layers where each layer has a single responsibility it makes testing so much easier.  For example in a typical web api backend, if your code has different layers to handle data fetching/persistence, business logic, and presentation transformations, each layer can test its own responsibilities without worrying about the rest.  Thinking about how to structure your code so it&rsquo;s easier to test.  This often means isolating complex business logic in a pure function that can be rigourously tested.  Depending where you are on the test coverage scale, you might determine tests for the complex business logic layer are sufficient and everything else is straightforward enough.</p>
<p>You might ask though, how can each layer not worry about the rest if each layer depends on other layers?  This is where <a href="https://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a> really shines.  You can build out a suite of reusable <a href="https://tyrrrz.me/blog/fakes-over-mocks">fake implementations</a> to inject into the layer you&rsquo;re testing against.  Building out full testing implementations of a layer may take longer initially but the time should be well spent when you can reuse them across all your tests, and have a single place to update when the layer behavior changes.  Contrast that with the <a href="https://jestjs.io/docs/mock-functions">Jest mocking</a> approach that monkey patches dependencies in each test and are nearly guaranteed to break across many tests when the layer behavior changes.</p>
<p>Another way to think about it is to think of your test suite as its own standalone application. It&rsquo;s &ldquo;just&rdquo; another program, which happens to have the requirements of verifying what another program does.  Don&rsquo;t throw out all your best practices and architecture for writing a good app when it comes to the test suite!  I&rsquo;ve seen test suites where there&rsquo;s tons of boilerplate setup code repeated everywhere and lots of inconsistencies between tests.  The test suite has to be maintained just like the application.</p>
<p>Ideally you want your tests as decoupled from the implementation as possible so it is easier to maintain.  You want to be able to assert on just the important logic (why your application exists in the first place) and have the tests remain the same as you refactor the implementation so you&rsquo;re sure it behaves the same. This of course is more feasible the higher up the testing pyramid you go, but still important in unit tests as well.  Test the interfaces, not the implementation details.</p>
<p>If the dev writing the code is also writing the tests, they have certain blind spots, assumptions, and probably a bit of laziness (*cough* guilty as charged) about how the feature will be used and what scenario to test.  Automated testing can get you far, but it&rsquo;s still not a complete substitute for real users prodding it. As an exercise, take the time to try and break your own code when you write it. Throw lots of data at it. Throw some <a href="https://github.com/minimaxir/big-list-of-naughty-strings">naughty data</a> at it.  <a href="https://en.wikipedia.org/wiki/Fuzzing">Fuzz it</a>. Try it in a different environment (hardware/OS/browser/etc). Get annoyed at it when it takes too long.  I can almost guarantee you&rsquo;ll find something you didn&rsquo;t think of in the routine testing motions.</p>
<p>The best time to find bugs is right after you wrote the code while everything is still fresh in your head. <a href="https://youtu.be/I845O57ZSy4?t=3410">John Carmack talks here</a> about running code with the debugger right after you write it to verify it&rsquo;s working like you expect it does.  I&rsquo;m similarly amazed that more developers don&rsquo;t do this since it&rsquo;s not only such a good way to catch bugs as soon as possible, but also a great way for <a href="/upgrade-your-brain-virtual-machine">Upgrading your Brain Virtual Machine</a>.  This is harder to do in environments where there&rsquo;s lots of async and framework code, but it&rsquo;s still worth doing when writing complex functions and will give you a better understanding of what all is going on.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Hopefully reading this has sparked some introspection on your own testing philosophy.  I didn&rsquo;t want to rehash specific testing methodologies that are well covered by other articles but instead get people thinking about the cost benefit analysis of the methods as applied to their situation.  Don&rsquo;t <em>blindly</em> follow advice from people.  Especially me</p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>Testing Next.JS with Cypress and Mock Service Worker</title>
      <description>If you&amp;rsquo;re running a Next.js app and want to test it with Cypress you probably noticed a problem when it came time to test a server side rendered page. There&amp;rsquo;s no built in way to mock out requests made in getServerSideProps. When I ran into this and searched around for solutions I found this article that has a good explination of the problem (and diagrams), but I wasn&amp;rsquo;t satisfied with any of the solutions.</description>
      <link>https://dillonshook.com/testing-nextjs-with-cypress-and-msw/</link>
      <pubDate>Tue, 08 Nov 2022 07:58:08 +0000</pubDate>
      
      <guid>https://dillonshook.com/testing-nextjs-with-cypress-and-msw/</guid>
      
      <content:encoded><![CDATA[<p>If you&rsquo;re running a <a href="https://nextjs.org/">Next.js</a> app and want to test it with <a href="https://www.cypress.io/">Cypress</a> you probably noticed a problem when it came time to test a server side rendered page.  There&rsquo;s no built in way to mock out requests made in <code>getServerSideProps</code>.  When I ran into this and searched around for solutions I found <a href="https://blog.byndyusoft.com/testing-next-js-website-with-cypress-a8475fd087e2">this article</a> that has a good explination of the problem (and diagrams), but I wasn&rsquo;t satisfied with any of the solutions.</p>
<p>Here&rsquo;s a quick overview of the problem, there&rsquo;s no way to intercept requests made from the Next.js node process:
<img src="the_problem.webp" alt="The Problem"></p>
<p>The article offers one solution of running the Next app inside the Cypress test runner and using Nock, but I didn&rsquo;t like that solution since it seems brittle to run in CI and also you would have to have separate mocking libraries for requests made on the frontend.  The article then goes on to describe a solution with <a href="https://mswjs.io/">Mock Service Worker</a> which looks appealing because it can run on both the server and the client, but there&rsquo;s still the catch of controlling it from the Cypress tests which the author doesn&rsquo;t appear to have actually solved.</p>
<p>We were in this &ldquo;trying to find a good way to mock&rdquo; boat a few months ago and despite finding a few articles on setting up MSW with Cypress and Next there wasn&rsquo;t anything that satisfied my goals. The <a href="https://github.com/vercel/next.js/tree/canary/examples/with-msw">the vercel MSW example</a> has the basics of setting up MSW doesn&rsquo;t include Cypress in the setup and doesn&rsquo;t have any suggestions on how to control MSW from a test.</p>
<p>When setting up a mocks for our tests I had a few main goals:</p>
<ul>
<li>One set of mocks for both server and client</li>
<li>Easy to use in tests, and easy to write handlers</li>
<li>Able to have tests set up specific mocking scenarios</li>
<li>Not rely on complicated CI environments</li>
<li>Use as few packages as possible</li>
<li>As simple as possible, but no simpler</li>
</ul>
<p>To recap, the couple of problems that make this hard are:</p>
<ul>
<li>Any network calls made in <code>getServerSideProps</code> are made in a node process, where the requests made from the browser are in a separate process</li>
<li>When testing, mocks must be set up <em>before</em> a page loads to guarantee all requests (including ones made while server side rendering) are mocked</li>
</ul>
<h2 id="the-solution">The Solution</h2>
<p>What I came up with certainly isn&rsquo;t perfect (see Gotchas below) but it satisfies the goals, works within the constraints of the versions of Next (12.x), Cypress (10.x), and MSW (0.44) we have, and is certainly better than our previous setup.</p>
<p>The solution I came up with uses cookies and custom next api&rsquo;s to trigger loading the mocks for tests.</p>
<p><img src="the_solution.webp" alt="The Solution"></p>
<p>With this setup your test code will be nice, simple and look like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>describe(<span style="color:#d14">&#34;a test&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  it(<span style="color:#d14">&#34;does something&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    cy.addMocks(<span style="color:#d14">&#34;mockFileName&#34;</span>);
</span></span><span style="display:flex;"><span>    cy.visit(<span style="color:#d14">`/a-path-to-test`</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">//then assert and interact as you would
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>And the handler files that will be used for server <em>and</em> client requests just export arrays of MSW handlers:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { RequestHandler, rest } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">const</span> handlers: <span style="color:#458;font-weight:bold">RequestHandler</span>[] <span style="color:#000;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  rest.<span style="color:#000;font-weight:bold">get</span>(<span style="color:#d14">&#34;*/some-api-to-mock&#34;</span>, (_req, res, ctx) <span style="color:#000;font-weight:bold">=&gt;</span> res(ctx.json([]))),
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><h2 id="the-details">The Details</h2>
<p>There&rsquo;s a lot of different stuff happening here in lots of different places so lets go through it step by step.</p>
<p>The entrypoint for setting up MSW is in <code>/pages/_app.page.tsx</code> which creates either the server side or client side MSW instance. It checks to see if one is already created to prevent creating duplicates which is important to not confuse which mock handlers are on which instance. There are also a couple environment variable checks to a) make sure MSW doesn&rsquo;t run in production and b) make sure it doesn&rsquo;t run in normal development unless you want it to. More on that in the running it section. Finally, it tracks the loading state of the mocks and renders a loader to prevent pages making requests before MSW is initialized.</p>
<p><code>_app.page.tsx</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">const</span> AppPage <span style="color:#000;font-weight:bold">=</span> ({ Component, pageProps, router }<span style="color:#000;font-weight:bold">:</span> AppProps) <span style="color:#000;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">const</span> [mswState, setMswState] <span style="color:#000;font-weight:bold">=</span> useState<span style="color:#000;font-weight:bold">&lt;</span><span style="color:#d14">&#34;unused&#34;</span> <span style="color:#000;font-weight:bold">|</span> <span style="color:#d14">&#34;loading&#34;</span> <span style="color:#000;font-weight:bold">|</span> <span style="color:#d14">&#34;ready&#34;</span><span style="color:#000;font-weight:bold">&gt;</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#d14">&#34;unused&#34;</span>
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>  <span style="color:#998;font-style:italic">// MSW requires initialization of its mock server inside the server code so it can intercept requests being
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  <span style="color:#998;font-style:italic">// made by Next in getServerSideProps. Unfortunately it cannot be initialized from Cypress since it&#39;s
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  <span style="color:#998;font-style:italic">// running in a separate process.  This should really run when the server starts up (not first request like it is)
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  <span style="color:#998;font-style:italic">// But there&#39;s not an easy way to do that now https://github.com/vercel/next.js/discussions/15341
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  <span style="color:#000;font-weight:bold">if</span> (process.env.NEXT_PUBLIC_IA_ENV <span style="color:#000;font-weight:bold">!==</span> <span style="color:#d14">&#34;production&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (process.env.NEXT_PUBLIC_MOCKS_ENABLED <span style="color:#000;font-weight:bold">&amp;&amp;</span> mswState <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;unused&#34;</span>) {
</span></span><span style="display:flex;"><span>      setMswState(<span style="color:#d14">&#34;loading&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">const</span> mockImport <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">import</span>(<span style="color:#d14">&#34;../src/mocks&#34;</span>);
</span></span><span style="display:flex;"><span>      mockImport
</span></span><span style="display:flex;"><span>        .then(({ initMocks }) <span style="color:#000;font-weight:bold">=&gt;</span> initMocks())
</span></span><span style="display:flex;"><span>        .then(() <span style="color:#000;font-weight:bold">=&gt;</span> setMswState(<span style="color:#d14">&#34;ready&#34;</span>));
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">if</span> (mswState <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;loading&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">// If MSW is enabled we must wait for it to start up before rendering to make sure all requests
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>    <span style="color:#998;font-style:italic">// are captured for consistency
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>    <span style="color:#000;font-weight:bold">return</span> &lt;<span style="color:#000080">Loader</span> /&gt;;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#998;font-style:italic">// normal return
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>};
</span></span></code></pre></div><p>The mocks index file is built off <a href="https://github.com/vercel/next.js/blob/canary/examples/with-msw/mocks/index.ts">the example</a> one but with the additional checks to make sure there&rsquo;s only a single instance of both the server and client MSW instance, as well as loading test handlers if the cookie is set.</p>
<p><code>/src/mocks/index.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> Cookies <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;js-cookie&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">async</span> <span style="color:#000;font-weight:bold">function</span> initMocks() {
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">if</span> (<span style="color:#000;font-weight:bold">typeof</span> <span style="color:#0086b3">window</span> <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;undefined&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">//Make sure there&#39;s only one instance of the server
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#000;font-weight:bold">typeof</span> <span style="color:#000;font-weight:bold">global</span>.serverMsw <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;undefined&#34;</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">const</span> { server } <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">await</span> <span style="color:#000;font-weight:bold">import</span>(<span style="color:#d14">&#34;./server&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">global</span>.serverMsw <span style="color:#000;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        server,
</span></span><span style="display:flex;"><span>      };
</span></span><span style="display:flex;"><span>      console.log(<span style="color:#d14">&#34;Initialized SERVER msw&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  } <span style="color:#000;font-weight:bold">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">//Make sure there&#39;s only one instance of the worker
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#0086b3">window</span>.msw <span style="color:#000;font-weight:bold">===</span> <span style="color:#000;font-weight:bold">undefined</span> <span style="color:#000;font-weight:bold">||</span> <span style="color:#0086b3">window</span>.msw.worker <span style="color:#000;font-weight:bold">===</span> <span style="color:#000;font-weight:bold">undefined</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">const</span> { worker } <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">await</span> <span style="color:#000;font-weight:bold">import</span>(<span style="color:#d14">&#34;./browser&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#0086b3">window</span>.msw <span style="color:#000;font-weight:bold">=</span> { worker };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      console.log(<span style="color:#d14">&#34;Initialized BROWSER msw&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">const</span> mockFile <span style="color:#000;font-weight:bold">=</span> Cookies.<span style="color:#000;font-weight:bold">get</span>(<span style="color:#d14">&#34;cypress:mock-file&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">if</span> (mockFile) {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> nextRequestRegex <span style="color:#000;font-weight:bold">=</span> <span style="color:#009926">/\/_next.*/</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">await</span> worker.start({
</span></span><span style="display:flex;"><span>          onUnhandledRequest<span style="color:#000;font-weight:bold">:</span> (req) <span style="color:#000;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#998;font-style:italic">// Don&#39;t warn about _next api call&#39;s not being mocked
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>            <span style="color:#000;font-weight:bold">if</span> (req.url.pathname.match(nextRequestRegex)) <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>            console.warn(
</span></span><span style="display:flex;"><span>              <span style="color:#d14">&#34;[MSW] unmocked request:&#34;</span>,
</span></span><span style="display:flex;"><span>              <span style="color:#d14">`</span><span style="color:#d14">${</span>req.method<span style="color:#d14">}</span><span style="color:#d14"> </span><span style="color:#d14">${</span>req.url.pathname<span style="color:#d14">}</span><span style="color:#d14">`</span>
</span></span><span style="display:flex;"><span>            );
</span></span><span style="display:flex;"><span>          },
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> allMocks <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">await</span> <span style="color:#000;font-weight:bold">import</span>(<span style="color:#d14">&#34;mocks/handlers&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> handlers <span style="color:#000;font-weight:bold">=</span> (allMocks <span style="color:#000;font-weight:bold">as</span> <span style="color:#458;font-weight:bold">any</span>)[mockFile].handlers;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        worker.use(...handlers);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        console.log(<span style="color:#d14">&#34;Added browser handlers for &#34;</span> <span style="color:#000;font-weight:bold">+</span> mockFile);
</span></span><span style="display:flex;"><span>      } <span style="color:#000;font-weight:bold">else</span> {
</span></span><span style="display:flex;"><span>        worker.resetHandlers();
</span></span><span style="display:flex;"><span>        worker.stop();
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> { initMocks };
</span></span></code></pre></div><p>Both server and client instances initialize using the global handlers file which handles all the general requests the app makes that aren&rsquo;t that page specific, and can be overwritten by test specific handlers.</p>
<p><code>src/mocks/browser.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { handlers } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;./globalHandlers&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { setupWorker } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">const</span> worker <span style="color:#000;font-weight:bold">=</span> setupWorker(...handlers);
</span></span></code></pre></div><br>
<p><code>src/mocks/server.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { handlers } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;./globalHandlers&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { setupServer } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw/node&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">const</span> server <span style="color:#000;font-weight:bold">=</span> setupServer(...handlers);
</span></span></code></pre></div><br>
<p><code>src/mocks/globalHandlers.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { RequestHandler, rest } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">const</span> handlers: <span style="color:#458;font-weight:bold">RequestHandler</span>[] <span style="color:#000;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  rest.<span style="color:#000;font-weight:bold">get</span>(<span style="color:#d14">&#34;*/some/api&#34;</span>, (_req, res, ctx) <span style="color:#000;font-weight:bold">=&gt;</span> res(ctx.json({}))),
</span></span><span style="display:flex;"><span>  ...otherHandlersYouHave,
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><p>We now need a way to tell our Next.js backend that we want to start mocking for a specific test, and then be able to reset the mocks after the test is done. By setting up a <a href="https://nextjs.org/docs/api-routes/introduction">Next API route</a> we can do just that!</p>
<p>The first api lets us add a mock file and start MSW on the server. A lot of it is error handling but the main part is importing the handlers index file, starting MSW, then the <code>server.use(...handlers)</code> call to add the mock handlers. The API also adds a <code>Set-Cookie</code> header which is important for using this setup outside of Cypress, but more on that later.</p>
<p><code>pages/api/test-mock/add.api.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { NextApiRequest, NextApiResponse } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;next&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { RequestHandler } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { serialize } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;cookie&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">default</span> <span style="color:#000;font-weight:bold">async</span> <span style="color:#000;font-weight:bold">function</span> handler(
</span></span><span style="display:flex;"><span>  req: <span style="color:#458;font-weight:bold">NextApiRequest</span>,
</span></span><span style="display:flex;"><span>  res: <span style="color:#458;font-weight:bold">NextApiResponse</span>
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">if</span> (process.env.NEXT_PUBLIC_IA_ENV <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;production&#34;</span>) {
</span></span><span style="display:flex;"><span>    res.status(<span style="color:#099">405</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">try</span> {
</span></span><span style="display:flex;"><span>    res.setHeader(<span style="color:#d14">&#34;Cache-Control&#34;</span>, <span style="color:#d14">&#34;no-cache&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> fileName <span style="color:#000;font-weight:bold">=</span> req.query.file;
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#000;font-weight:bold">!</span>fileName <span style="color:#000;font-weight:bold">||</span> <span style="color:#000;font-weight:bold">typeof</span> fileName <span style="color:#000;font-weight:bold">!==</span> <span style="color:#d14">&#34;string&#34;</span>) {
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#099">400</span>).json({ status<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;Missing file param&#34;</span>, fileName });
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> allMocks <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">await</span> <span style="color:#000;font-weight:bold">import</span>(<span style="color:#d14">&#34;mocks/handlers&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> handlers: <span style="color:#458;font-weight:bold">RequestHandler</span>[] <span style="color:#000;font-weight:bold">=</span>
</span></span><span style="display:flex;"><span>      allMocks[fileName <span style="color:#000;font-weight:bold">as</span> <span style="color:#000;font-weight:bold">keyof</span> <span style="color:#000;font-weight:bold">typeof</span> allMocks].handlers;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">typeof</span> <span style="color:#000;font-weight:bold">global</span>.serverMsw <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;undefined&#34;</span> <span style="color:#000;font-weight:bold">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>      process.env.NEXT_PUBLIC_MOCKS_ENABLED
</span></span><span style="display:flex;"><span>    ) {
</span></span><span style="display:flex;"><span>      <span style="color:#998;font-style:italic">//Auto start up MSW if it hasn&#39;t been yet.  This can happen if this is the first request
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>      <span style="color:#998;font-style:italic">//from cypress before a page has loaded
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>      <span style="color:#000;font-weight:bold">const</span> { initMocks } <span style="color:#000;font-weight:bold">=</span> <span style="color:#000;font-weight:bold">await</span> <span style="color:#000;font-weight:bold">import</span>(<span style="color:#d14">&#34;mocks&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">await</span> initMocks();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#000;font-weight:bold">typeof</span> <span style="color:#000;font-weight:bold">global</span>.serverMsw <span style="color:#000;font-weight:bold">!==</span> <span style="color:#d14">&#34;undefined&#34;</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">global</span>.serverMsw.server.listen({ onUnhandledRequest<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;warn&#34;</span> });
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">global</span>.serverMsw.server.use(...handlers);
</span></span><span style="display:flex;"><span>      res.setHeader(
</span></span><span style="display:flex;"><span>        <span style="color:#d14">&#34;Set-Cookie&#34;</span>,
</span></span><span style="display:flex;"><span>        serialize(<span style="color:#d14">&#34;cypress:mock-file&#34;</span>, fileName, {
</span></span><span style="display:flex;"><span>          path<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;/&#34;</span>,
</span></span><span style="display:flex;"><span>          sameSite<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;lax&#34;</span>,
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>      );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#099">200</span>).json({
</span></span><span style="display:flex;"><span>        status<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;Added Server Handlers&#34;</span>,
</span></span><span style="display:flex;"><span>        handlers: <span style="color:#458;font-weight:bold">handlers.map</span>((h) <span style="color:#000;font-weight:bold">=&gt;</span> h.info.header),
</span></span><span style="display:flex;"><span>      });
</span></span><span style="display:flex;"><span>    } <span style="color:#000;font-weight:bold">else</span> {
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#099">400</span>).json({ status<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;Mock server not initialized.&#34;</span> });
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  } <span style="color:#000;font-weight:bold">catch</span> (e: <span style="color:#458;font-weight:bold">any</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (req.query.file) {
</span></span><span style="display:flex;"><span>      console.error(<span style="color:#d14">&#34;Error trying to add mock &#34;</span> <span style="color:#000;font-weight:bold">+</span> req.query.file);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    console.error(e);
</span></span><span style="display:flex;"><span>    res.status(<span style="color:#099">500</span>).json({ error: <span style="color:#458;font-weight:bold">e.toString</span>() });
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The reset api is very straight forward to stop the server MSW instance</p>
<p><code>pages/api/test-mock/reset.api.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { NextApiRequest, NextApiResponse } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;next&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { serialize } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;cookie&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">default</span> <span style="color:#000;font-weight:bold">async</span> <span style="color:#000;font-weight:bold">function</span> handler(
</span></span><span style="display:flex;"><span>  req: <span style="color:#458;font-weight:bold">NextApiRequest</span>,
</span></span><span style="display:flex;"><span>  res: <span style="color:#458;font-weight:bold">NextApiResponse</span>
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">if</span> (process.env.NEXT_PUBLIC_IA_ENV <span style="color:#000;font-weight:bold">===</span> <span style="color:#d14">&#34;production&#34;</span>) {
</span></span><span style="display:flex;"><span>    res.status(<span style="color:#099">405</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">try</span> {
</span></span><span style="display:flex;"><span>    res.setHeader(<span style="color:#d14">&#34;Cache-Control&#34;</span>, <span style="color:#d14">&#34;no-cache&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#000;font-weight:bold">typeof</span> <span style="color:#000;font-weight:bold">global</span>.serverMsw <span style="color:#000;font-weight:bold">!==</span> <span style="color:#d14">&#34;undefined&#34;</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">global</span>.serverMsw.server.resetHandlers();
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">global</span>.serverMsw.server.close();
</span></span><span style="display:flex;"><span>      res.setHeader(
</span></span><span style="display:flex;"><span>        <span style="color:#d14">&#34;Set-Cookie&#34;</span>,
</span></span><span style="display:flex;"><span>        serialize(<span style="color:#d14">&#34;cypress:mock-file&#34;</span>, <span style="color:#d14">&#34;&#34;</span>, {
</span></span><span style="display:flex;"><span>          path<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;/&#34;</span>,
</span></span><span style="display:flex;"><span>          sameSite<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;lax&#34;</span>,
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>      );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#099">200</span>).json({ status<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;Reset Handlers&#34;</span> });
</span></span><span style="display:flex;"><span>    } <span style="color:#000;font-weight:bold">else</span> {
</span></span><span style="display:flex;"><span>      res.status(<span style="color:#099">400</span>).json({ status<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;Mock server not initialized&#34;</span> });
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  } <span style="color:#000;font-weight:bold">catch</span> (e: <span style="color:#458;font-weight:bold">any</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">// eslint-disable-next-line no-console
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>    console.error(e);
</span></span><span style="display:flex;"><span>    res.status(<span style="color:#099">500</span>).json({ error: <span style="color:#458;font-weight:bold">e.toString</span>() });
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Ok we now have most of the plumbing going but now we need to set up a way to use test specific handlers. I originally had dynamic imports set up so no index file would be necessary but there&rsquo;s <a href="https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import">a limitation with webpack</a> on <em>how</em> dynamic the path can be which means the whole path can&rsquo;t be dynamic, only the filename. This meant every test handler file had to go in one directory and quickly became an unorganized mess. So to fix that (as well as fixing file change auto restarts which were broken with dynamic imports) we switched to a handler index file that exports all the test specific handlers. <em>An important part here is that the name that they&rsquo;re exported as is the canonical name you&rsquo;ll use to add them in tests.</em></p>
<p><code>src/mocks/handlers/index.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">*</span> <span style="color:#000;font-weight:bold">as</span> mockFileName <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;./testName/mockFileName&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic">// ... there will be a whole bunch more for different tests and scenarios
</span></span></span></code></pre></div><p>The test specific handlers just export an array of MSW handlers which can be as complicated or as simple as your test needs. Remember that these handlers complement everything in the <code>globalHandlers.ts</code> file. New api&rsquo;s can be handled here and global handlers can be overwritten for a specific test scenario.</p>
<p><code>src/mocks/handlers/testName/mockFileName</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { RequestHandler, rest } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">const</span> handlers: <span style="color:#458;font-weight:bold">RequestHandler</span>[] <span style="color:#000;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  rest.<span style="color:#000;font-weight:bold">get</span>(<span style="color:#d14">&#34;*/exhibits&#34;</span>, (_req, res, ctx) <span style="color:#000;font-weight:bold">=&gt;</span> res(ctx.json([]))),
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><p>Finally we need to integrate with Cypress to make it easy to add mocks so we&rsquo;ll use custom Cypress commands to add and reset mocks.</p>
<p><code>cypress/support/commands.js</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>Cypress.Commands.add(<span style="color:#d14">&#34;addMocks&#34;</span>, (fileName) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#998;font-style:italic">//Set cookie is still necessary here even though the api sets cookies to get it
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  <span style="color:#998;font-style:italic">//set on the right cypress browser instance
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  cy.setCookie(<span style="color:#d14">&#34;cypress:mock-file&#34;</span>, fileName);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  cy.request({
</span></span><span style="display:flex;"><span>    method<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;GET&#34;</span>,
</span></span><span style="display:flex;"><span>    url<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">`/api/test-mock/add?file=</span><span style="color:#d14">${</span><span style="color:#0086b3">encodeURIComponent</span>(fileName)<span style="color:#d14">}</span><span style="color:#d14">`</span>,
</span></span><span style="display:flex;"><span>    failOnStatusCode<span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">true</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Cypress.Commands.add(<span style="color:#d14">&#34;resetMocks&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  cy.clearCookie(<span style="color:#d14">&#34;cypress:mock-file&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#998;font-style:italic">// Reset snooped requests and listeners
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  cy.<span style="color:#0086b3">window</span>().then((<span style="color:#0086b3">window</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> worker <span style="color:#000;font-weight:bold">=</span> <span style="color:#0086b3">window</span><span style="color:#000;font-weight:bold">?</span>.msw<span style="color:#000;font-weight:bold">?</span>.worker;
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (worker) {
</span></span><span style="display:flex;"><span>      worker.events.removeAllListeners();
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  mockedRequests <span style="color:#000;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">return</span> cy.request({
</span></span><span style="display:flex;"><span>    method<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;GET&#34;</span>,
</span></span><span style="display:flex;"><span>    url<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">`/api/test-mock/reset`</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">// Turn off failing on status code for smoke tests that won&#39;t have mocks set up
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>    failOnStatusCode<span style="color:#000;font-weight:bold">:</span> <span style="color:#000;font-weight:bold">false</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">let</span> mockedRequests <span style="color:#000;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Cypress.Commands.add(<span style="color:#d14">&#34;startSnoopingBrowserMockedRequest&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  cy.<span style="color:#0086b3">window</span>().then((<span style="color:#0086b3">window</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> worker <span style="color:#000;font-weight:bold">=</span> <span style="color:#0086b3">window</span><span style="color:#000;font-weight:bold">?</span>.msw<span style="color:#000;font-weight:bold">?</span>.worker;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (<span style="color:#000;font-weight:bold">!</span>worker) {
</span></span><span style="display:flex;"><span>      reject(<span style="color:#000;font-weight:bold">new</span> <span style="color:#0086b3">Error</span>(<span style="color:#d14">`MSW Browser worker not instantiated`</span>));
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    worker.events.on(<span style="color:#d14">&#34;request:match&#34;</span>, (req) =&gt; {
</span></span><span style="display:flex;"><span>      mockedRequests.push(req);
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic">/**
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"> * URL is a pattern matching URL that uses the same behavior as handlers URL matching
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"> * e.g. &#39;* /events/groups/:groupId&#39; without the space
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"> */</span>
</span></span><span style="display:flex;"><span>Cypress.Commands.add(<span style="color:#d14">&#34;findBrowserMockedRequests&#34;</span>, ({ method, url }) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">return</span> <span style="color:#000;font-weight:bold">new</span> Cypress.<span style="color:#0086b3">Promise</span>((resolve, reject) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">!</span>method <span style="color:#000;font-weight:bold">||</span>
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">!</span>url <span style="color:#000;font-weight:bold">||</span>
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">typeof</span> method <span style="color:#000;font-weight:bold">!==</span> <span style="color:#d14">&#34;string&#34;</span> <span style="color:#000;font-weight:bold">||</span>
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">typeof</span> url <span style="color:#000;font-weight:bold">!==</span> <span style="color:#d14">&#34;string&#34;</span>
</span></span><span style="display:flex;"><span>    ) {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">return</span> reject(<span style="color:#d14">`Invalid parameters passed. Method: </span><span style="color:#d14">${</span>method<span style="color:#d14">}</span><span style="color:#d14"> Url: </span><span style="color:#d14">${</span>url<span style="color:#d14">}</span><span style="color:#d14">`</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    resolve(
</span></span><span style="display:flex;"><span>      mockedRequests.filter((req) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> matchesMethod <span style="color:#000;font-weight:bold">=</span>
</span></span><span style="display:flex;"><span>          req.method <span style="color:#000;font-weight:bold">&amp;&amp;</span> req.method.toLowerCase() <span style="color:#000;font-weight:bold">===</span> method.toLowerCase();
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">const</span> matchesUrl <span style="color:#000;font-weight:bold">=</span> matchRequestUrl(req.url, url).matches;
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">return</span> matchesMethod <span style="color:#000;font-weight:bold">&amp;&amp;</span> matchesUrl;
</span></span><span style="display:flex;"><span>      })
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>And in the e2e file we&rsquo;ll make sure to reset the mocks after each test:</p>
<p><code>cypress/support/e2e.js</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#998;font-style:italic">// Always reset the MSW mock overrides between each test so they don&#39;t pollute each other
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>afterEach(() =&gt; {
</span></span><span style="display:flex;"><span>  cy.resetMocks();
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h3 id="mocked-request-assertion">Mocked Request Assertion</h3>
<p>You probably noticed there&rsquo;s a bit of extra stuff in the Cypress commands for snooping on network requests. The <a href="https://mswjs.io/docs/recipes/request-assertions">the official MSW doc says</a>:</p>
<blockquote>
<p>When testing, it may be tempting to write assertions against a dispatched request. Adding such assertions, however, is implementation details testing and is highly discouraged. Asserting requests in such way is testing how your application is written, instead of what it does.</p>
</blockquote>
<p>I disagree with this take however since a big part what an application does is interact with APIs. You probably want to know if you&rsquo;re screwing up a critical API call when testing. So it can be very valuable to assert that requests were made correctly, especially POST/PATCH requests.</p>
<p>To do this, add the following to your test body:
<code>cy.startSnoopingBrowserMockedRequest();</code></p>
<p>This will start capturing all the subsequent requests for the test that are mocked out by MSW. Important to note in the name, this will only capture the <em>browser client side requests</em> due to the MSW setup.</p>
<p>Once the test performs whatever action it needs to, you can then get all matching requests and assert like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>cy.findBrowserMockedRequests({ method<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;PATCH&#34;</span>, url<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;*/an/api/:params&#34;</span> }).then(
</span></span><span style="display:flex;"><span>  (patchRequests) =&gt; {
</span></span><span style="display:flex;"><span>    assert.equal(patchRequests.length, <span style="color:#099">1</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> body <span style="color:#000;font-weight:bold">=</span> patchRequests[<span style="color:#099">0</span>].body;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    assert.equal(body.data, { something });
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>The <code>resetMocks</code> command run after each test will reset the snooping and request log so tests don&rsquo;t pollute each other.</p>
<h3 id="test-code">Test Code</h3>
<p>Once all that&rsquo;s set up your test code will look like</p>
<p><code>cypress/e2e/specs/sample.js</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { dataOrIdsMocked } from <span style="color:#d14">&#34;mocks/handlers/testName/mockFileName&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>describe(<span style="color:#d14">&#34;a test&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  it(<span style="color:#d14">&#34;does something&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    cy.addMocks(<span style="color:#d14">&#34;mockFileName&#34;</span>);
</span></span><span style="display:flex;"><span>    cy.visit(<span style="color:#d14">`/a-path-to-test`</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">//then assert and interact as you would, using imported dataOrIdsMocked as needed
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="running-it">Running It</h2>
<p>You can set up your dev scripts however you want of course as long as the right environment variables are set up, but here&rsquo;s a suggested way to set it up so locally you can just run <code>yarn cypress</code> which will start both the Next.js server and Cypress with all the config handled using the <a href="https://www.npmjs.com/package/concurrently">concurrently npm package</a>.</p>
<p><code>package.json</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#000080">&#34;scripts&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;dev:mocked&#34;</span>: <span style="color:#d14">&#34;NEXT_PUBLIC_MOCKS_ENABLED=1 yarn dev&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;dev&#34;</span>: <span style="color:#d14">&#34;concurrently -n NEXT,TS -c magenta,cyan \&#34;yarn dev:start\&#34; \&#34;yarn ts --watch --pretty\&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;cypress:config-and-open&#34;</span>: <span style="color:#d14">&#34;node_modules/.bin/cypress open --config specPattern=cypress/e2e/specs&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#000080">&#34;cypress&#34;</span>: <span style="color:#d14">&#34;concurrently -n Dev,Cypress -c blue,yellow  \&#34;yarn dev:mocked\&#34; \&#34;yarn cypress:config-and-open\&#34;&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A real nice bonus about getting MSW set up like this is using it outside of testing just for developing new features where an API isn&rsquo;t finished yet. Devs can just run <code>yarn dev:mocked</code> which will start up Next with the mocks enabled, they can set up a handler file the same as they would for a test scenario, manually go to <code>/api/test-mock/add?file=mockFileName</code>, and then use the app as normal with a new API mocked out. Once they&rsquo;re done developing, that mock handler file can then be directly turned into a Cypress test.</p>
<h2 id="gotchas">Gotchas</h2>
<p>As said in the intro, this setup is not without flaws that I wish had solutions. If you can think of solutions please leave them in the comments!</p>
<h3 id="1-mocks-must-be-added-before-navigating-to-a-page">1. Mocks must be added before navigating to a page</h3>
<p>Because mocks must be loaded before a page starts loaded so no requests are missed, they must be added before navigating to a page. The browser side setup loads the mocks as the page is loading.</p>
<p>So you can&rsquo;t call <code>cy.addMocks</code> multiple times in the same test file unless you are visiting multiple pages in the same test.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>cy.addMocks(<span style="color:#d14">&#34;some-mock-file&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cy.visit(<span style="color:#d14">&#34;page1&#34;</span>); <span style="color:#998;font-style:italic">// some-mock-file mocks added here
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>
</span></span><span style="display:flex;"><span>cy.addMocks(<span style="color:#d14">&#34;more-mocks&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic">// more-mocks haven&#39;t been loaded in yet
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>cy.get(<span style="color:#d14">&#34;[data-cy=abc123]&#34;</span>).should(<span style="color:#d14">&#34;exist&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic">// now the more-mocks get loaded in
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>cy.visit(<span style="color:#d14">&#34;page2&#34;</span>);
</span></span></code></pre></div><h3 id="2-its-hard-to-dynamically-change-mocks-during-or-between-tests">2. It&rsquo;s hard to dynamically change mocks during or between tests</h3>
<p>If you need the same endpoint to return different values throughout a test file there&rsquo;s two strategies:</p>
<p>Create multiple mock files and add them in where needed.</p>
<p><code>cypress/e2e/specs/sample.js</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>describe(<span style="color:#d14">&#34;a test&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  it(<span style="color:#d14">&#34;does something&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    cy.addMocks(<span style="color:#d14">&#34;mockFileName&#34;</span>);
</span></span><span style="display:flex;"><span>    cy.visit(<span style="color:#d14">`/a-path-to-test`</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  it(<span style="color:#d14">&#34;does something else&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    cy.addMocks(<span style="color:#d14">&#34;mockFileNameWithDifferences&#34;</span>);
</span></span><span style="display:flex;"><span>    cy.visit(<span style="color:#d14">`/a-path-to-test`</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">//...
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>This is usually the best course of action since it makes it clear what the mock setup for each test is. It does lead to lots of handler files with slight differences between them so you&rsquo;ll need to use normal software best practices to not duplicate a whole bunch of stuff in each handler file. Util functions shared between handlers that return arrays of handlers are a good way to do that.</p>
<p>An alternative approach that works better for some kinds of tests is to use one handler file, but maintain state in it as requests are made. See next point for the caveat about this but here&rsquo;s an example of what that looks like:</p>
<p><code>/mocks/handlers/statefulHandler.ts</code></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">import</span> { RequestHandler, rest } <span style="color:#000;font-weight:bold">from</span> <span style="color:#d14">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">let</span> testState <span style="color:#000;font-weight:bold">=</span> <span style="color:#099">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">const</span> returnObjects <span style="color:#000;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  { status: <span style="color:#458;font-weight:bold">404</span>, body<span style="color:#000;font-weight:bold">:</span> { error<span style="color:#000;font-weight:bold">:</span> <span style="color:#d14">&#34;Testing error&#34;</span> } },
</span></span><span style="display:flex;"><span>  { status: <span style="color:#458;font-weight:bold">200</span>, body<span style="color:#000;font-weight:bold">:</span> { someObject } },
</span></span><span style="display:flex;"><span>  { status: <span style="color:#458;font-weight:bold">200</span>, body<span style="color:#000;font-weight:bold">:</span> { someOtherObject } },
</span></span><span style="display:flex;"><span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#000;font-weight:bold">export</span> <span style="color:#000;font-weight:bold">const</span> handlers: <span style="color:#458;font-weight:bold">RequestHandler</span>[] <span style="color:#000;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>  rest.<span style="color:#000;font-weight:bold">get</span>(<span style="color:#d14">&#34;*/jobs&#34;</span>, (_req, res, ctx) <span style="color:#000;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> ret <span style="color:#000;font-weight:bold">=</span> returnObjects[testState];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">if</span> (testState <span style="color:#000;font-weight:bold">===</span> <span style="color:#099">1</span>) {
</span></span><span style="display:flex;"><span>      testState <span style="color:#000;font-weight:bold">=</span> <span style="color:#099">2</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span> res(ctx.status(ret.status), ctx.json(ret.body));
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>  rest.post(<span style="color:#d14">&#34;*/jobs&#34;</span>, (_req, res, ctx) <span style="color:#000;font-weight:bold">=&gt;</span> {
</span></span><span style="display:flex;"><span>    testState <span style="color:#000;font-weight:bold">=</span> <span style="color:#099">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span> res(ctx.json({ whatever }));
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><h3 id="3-state-is-tricky-in-handlers">3. State is tricky in handlers</h3>
<p>In the above example there&rsquo;s a big gotcha: the handler state is independent between the client and server. So if some requests are made in server side rendering then the browser makes some requests, each will start at the initial state. This is of course because of the two different instances of MSW, one on the server, one on the browser.</p>
<h3 id="4-mock-files-get-imported-multiple-times">4. Mock files get imported multiple times</h3>
<p>The mock handler files get imported by several places like when the page loads, and when the server side mocks are enabled via API. If the tests are importing ids or other data from the handlers, these must be static values. One thing that tripped us up was using sequenced id&rsquo;s in factories created by the <a href="https://www.npmjs.com/package/fishery">fishery</a> library. If you try this you&rsquo;ll find that the test import will get a different value returned than the mocks are using.</p>
<h2 id="the-end">The End</h2>
<p>Hope you found this useful, at the very least as another approach to consider. I spent way more time than I thought getting Next, Cypress, and Mock Service Work set up in a way I was ok with. There were some good articles out there on basics but nothing that covered everything I wanted so here I am filling in some gaps.</p>
<p>If you think you have a better way, or something to improve upon with this approach please let me know!</p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>What it Takes to Make a Game by Yourself</title>
      <description>Macrocosm is a mobile game that takes you from atom to galactic empire across seven interconnected stages where making progress in one stage gives you a boost in the next! This post is a deep dive into the (nearly) four years of free time I spent making it.</description>
      <link>https://dillonshook.com/what-it-takes-to-make-a-game-by-yourself/</link>
      <pubDate>Thu, 15 Sep 2022 16:37:00 +0000</pubDate>
      
      <guid>https://dillonshook.com/what-it-takes-to-make-a-game-by-yourself/</guid>
      
      <media:content url="/what-it-takes-to-make-a-game-by-yourself/cover_hu730431cab1811627906ac06c7d40512a_175310_720x0_resize_q85_h2_box.webp" medium="image" />
      
      <content:encoded><![CDATA[<p>The short answer is perseverance, skills and the ability to teach yourself new ones, creative problem solving, learning from mistakes, and a looot of time.  That&rsquo;s my take anyway.  Read on if you&rsquo;re curious where I&rsquo;m coming from!</p>
<h1 id="the-rest-of-the-intro">The Rest of the Intro</h1>
<p>I recently released <a href="https://macrocosm-game.com">Macrocosm</a> which is a mobile game that takes you from atom to galactic empire across seven interconnected stages where making progress in one stage gives you a boost in the next.  This post is a deep dive into the (nearly) four years of free time I spent making it.  Part catharsis, part brain dump while things are still mostly fresh, but mainly giving back to the game dev community, this post is for anyone currently making an indie game or thinking about it.</p>
<p>First, let&rsquo;s start with a few stats, because who doesn&rsquo;t love those, and it also should give you a better sense of the scope of the project.</p>
<h1 id="by-the-numbers">By the numbers</h1>
<ul>
<li>First commit June 2nd 2018.</li>
<li>594 Trello cards created to keep track of tasks and bugs</li>
<li>677 days worked on</li>
<li>2097 commits (so far)</li>
<li>Most productive days were 2/23/2020 and 9/20/2020 with 13 commits each.</li>
</ul>
<img alt="Commits Per Month" title="Commits Per Month" src="https://res.cloudinary.com/dillonshook/image/upload/v1662401102/macrocosm/Fork_a6h7FB2GVC.png" >
<img alt="Commits Per Weekday" title="Commits Per Weekday" src="https://res.cloudinary.com/dillonshook/image/upload/v1662401102/macrocosm/Fork_aJxvHhPsMG.png" >
<img alt="Commits Per Day Hour" title="Commits Per Day Hour" src="https://res.cloudinary.com/dillonshook/image/upload/v1662401102/macrocosm/Fork_w63aElo9nP.png" >
<h3 id="lines-of-code">Lines of Code</h3>
<p>Without 3rd party libraries &amp; dependencies:</p>
<pre tabindex="0"><code>--------------------------------------------------------------------
Language          files          blank        comment           code
--------------------------------------------------------------------
C#                  321           8377           2789          39958
HLSL                  9            102              5            517
Javascript            1              7              1             44
Bourne Shell          1              8              5             23
JSON                  1              0              0              9
--------------------------------------------------------------------
SUM:                333           8494           2800          40551
--------------------------------------------------------------------
</code></pre><p>With 3rd party code:</p>
<pre tabindex="0"><code>--------------------------------------------------------------------
Language          files          blank        comment           code
--------------------------------------------------------------------
C#                 1031          23359          23148         117836
HLSL                201           1077            280          18038
JSON                 26              1              0          10365
Swift                 1             65             10            375
Objective C           1             24              7            109
Objective C++         1             18              5             62
Javascript            1              7              1             44
XML                   1              2              0             29
Bourne Shell          1              8              5             23
C/C++ Header          1              1              0              4
--------------------------------------------------------------------
SUM:               1265          24562          23456         146885
--------------------------------------------------------------------
</code></pre><h3 id="back-of-the-napkin-hours-worked">Back of the Napkin Hours Worked</h3>
<p>Using the <a href="https://github.com/kimmobrunfeldt/git-hours">git-hours</a> tool (and <a href="https://github.com/kimmobrunfeldt/git-hours/pull/68">having to fix a bug</a> in the repo along the way!) I used an estimate of 30 minutes before first commit in a session to come up with a very back of the napkin estimate of 1,366 hours. This only takes into account the coding time and the art I did myself, not any of the off computer design, testing, organization, etc.  If I had to guess I&rsquo;d tack on another 300 hours or so for all that.</p>
<h3 id="budget">Budget</h3>
<p>I tried to be pretty frugal with how much I spent.  Both because it&rsquo;s my hobby side project, and because part of success to me was just being able to break even on costs which is easier when you have less of a hole to dig yourself out of :)</p>
<table>
<thead>
<tr>
<th>Expense</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>3rd Party Assets &amp; Plugins</td>
<td>$264.60</td>
</tr>
<tr>
<td>Business Costs</td>
<td>$1,089.00</td>
</tr>
<tr>
<td>Contractors</td>
<td>$3,781.88</td>
</tr>
<tr>
<td>Web, Email, &amp; LFS Hosting</td>
<td>$162.98</td>
</tr>
<tr>
<td>Apple Fees</td>
<td>$340.57</td>
</tr>
<tr>
<td>Unity Cloud Build</td>
<td>$210.32</td>
</tr>
<tr>
<td>&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;-</td>
<td>&mdash;&mdash;&mdash;</td>
</tr>
<tr>
<td>Total</td>
<td>$5,849.35</td>
</tr>
</tbody>
</table>
<h1 id="early-project-times">Early Project Times</h1>
<p>After coming to the tough realization I wouldn&rsquo;t be able to finish my previous project Solaria Tactics that I&rsquo;d been working on for a couple years I took some time off working on side projects.  Around that time <a href="https://www.decisionproblem.com/paperclips/index2.html">Universal Paperclips</a> spread through the office and internet like fire and I loved how it kept you on your toes for what would be next and what new gameplay would be unlocked.  I still had the drive inside of me wanting to release a game that I could be proud of and actually release to the world, so it didn&rsquo;t take much of a spark to get my brain thinking of game ideas again.  Like Universal Paperclips, I wanted to take the player from something humble and small to something gigantic and have a variety of gameplay along the way to keep it all fresh and new.</p>
<p>So I wrote down a few design pillars:</p>
<ul>
<li>Variety of gameplay</li>
<li>Connected stages</li>
<li>Can focus on your favorite stage, don&rsquo;t let the player get stuck</li>
<li>Lower stage progression acts as a force multiplier for later stages</li>
</ul>
<p>then brainstormed some ideas on what the stages would be, then got back to work! :D  Two days after the first commit I had this</p>
<video controls autoplay="autoplay" loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/q_auto:good/v1662401959/macrocosm/shooty_shooty.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>Which if you can squint hard enough, still forms the basis for stage one today!  You can also see my fascination with MASSIVE NUMBERS that was coming from paperclip land.  More on that later&hellip;</p>
<p>At this point in the process, I was thinking I&rsquo;d spend about 1-2 months on each of the 7 stages and wrap up the project in about a year.  Spoiler alert: that didn&rsquo;t happen lol.</p>
<p>One of the early challenges was figuring out how the stages would work together.  At one point I was thinking there would be some linear path where the game would automatically bounce you between stages as you made progress in some sort of <a href="https://en.wikipedia.org/wiki/Tower_of_Hanoi">Tower of Hanoi</a>-like pattern.  This got tricky to design very quickly as I was writing it down, and luckily was convinced by a friend to change it pretty early on and let the player decide when to switch stages.</p>
<p>There was some interview with Chris Taylor about the making of <a href="https://store.steampowered.com/app/9350/Supreme_Commander/">Supreme Commander</a> (still one of my favorite games) I read a long time ago where he was describing that as the game was being made, naturally everyone got better. The art made 10 months ago wasn&rsquo;t as good as the the art being made now.  That lead them to working on the game in passes where they would try to make things as good and fleshed out as they can, then go back in another pass and improve everything.</p>
<p>So this is how I thought about working on Macrocosm.  I would first make all the seven stages in a first pass rough draft state where they had the basic gameplay I thought would work and very placeholder everything else, then do another pass to refine, and repeat till release!  I did follow that model throughout the project and think it worked pretty well for the project overall, especially given the connected nature of it all.  I think it did save me from throwing away work and art that otherwise would have gone to waste if I would have tried to polish or finalize things too early.</p>
<h4 id="stage-2">Stage 2</h4>
<p>Less than a month after starting the project I was onto drafting the second stage</p>
<video controls autoplay="autoplay" loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/q_auto:good/v1662402287/macrocosm/early_snake.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>Working on the snake movement code was  one of those things where it&rsquo;s easy to get the basic version of it working, but surprisingly nuanced and complicated to get it to feel and work exactly like you want it to.  I ended up coming back to it a few times over the course of the development to refine.  In this first version you see it&rsquo;s doing the approach of having each part of the chain <a href="https://en.wikipedia.org/wiki/Linear_interpolation">lerp</a> towards it&rsquo;s immediate neighbor each frame.  The problem with this approach is that as the snake does a bunch of tight turns the pieces of the chain down the line start to smooth out and follow a straighter line path instead of exactly where the head actually went.   The other problem is that it&rsquo;s hard to make the head piece follow a nice curve while turning so you can&rsquo;t immediately turn back on yourself and break the chain which feels bad.  I ended up with a sparsely populated circular buffer of points where the head went and then code to figure out which point to go to next.  Lemme know if you know a better way :)</p>
<h4 id="stage-3">Stage 3</h4>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/q_auto:good/v1662402386/macrocosm/slide_beats.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>The first basic version of stage 3 came along pretty smoothly, though this was my first foray into the world of getting audio syncing up with gameplay.  This is another one of those &ldquo;oh that sounds easy&rdquo; problems that is very much not.  I found <a href="https://johnleonardfrench.com/ultimate-guide-to-playscheduled-in-unity/">these</a>  <a href="https://exceed7.com/native-audio/rhythm-game-crash-course/index.html">guides</a> that were immensely helpful though.  As the rhythm game crash course says:</p>
<blockquote>
<p>In rhythm game first you have to get the backing track to line up  with the first &ldquo;note&rdquo; (what is that depending on your game) and the rest  will stay correct UNLESS the game lags or the audio lags. 90% of the time the game lags and audio went ahead of the game since audio is not  in the same processing unit with the game anymore after the play command. The lag requires separated resolution and I will not talk about it right now.</p>
</blockquote>
<blockquote>
<p>Audio in Unity is &ldquo;fire and forget&rdquo;. When you ask Unity <code>AudioSource</code> to play it will take <strong>variable</strong> amount of time and play whenever it feels like.</p>
</blockquote>
<p>The really tricky part is that your gameplay code only runs ever X milliseconds based on your frame rate, but the music is continuously playing.  So between each time the update loop runs there&rsquo;s a different amount of music that has played.  It takes some careful math calculations and using</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-C#" data-lang="C#"><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">public</span> <span style="color:#458;font-weight:bold">double</span> MusicTimeElapsed {
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">get</span>{ <span style="color:#000;font-weight:bold">return</span> (<span style="color:#458;font-weight:bold">double</span>)musicSource.timeSamples / musicSource.clip.frequency; }
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>to synchronize gameplay code to spawn things on the beat of the song.</p>
<h4 id="stage-4">Stage 4</h4>
<p>When I was first started thinking about what I&rsquo;d do for stage four I went down this big rabbit hole thinking of a platformer where you controlled a creature going around collecting DNA points sonic style (or perhaps <a href="https://en.wikipedia.org/wiki/E.V.O.:_Search_for_Eden">EVO style</a>) and you could choose which traits to unlock to make your creature hold its breath longer, jump further, or go faster and let you get to other areas of the map like a metroidvania. I went so far as to sketch out a whole map of how it would look before coming to terms with how hard that all would be to implement and play well.  Thankfully I made that realization before starting to build any of it and brainstormed other ideas.</p>
<p>I ended up going with a roguelike partially inspired by the <a href="https://www.northstargames.com/products/evolution">Evolution</a> board game that fit my goals for the stage:</p>
<ul>
<li>Have unbounded replayability</li>
<li>Consumes lives naturally</li>
<li>Have some creature customization</li>
<li>Balance skill with upgrade power</li>
</ul>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/q_auto:good/v1662402557/macrocosm/creature_wheel.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>As usual, I wanted to go crazy with lots of creature adaptations that you could choose from that have gameplay impact, but more on that later.  Along the way doing research for the stage I did find this <a href="http://www.onezoom.org/life.html/@Huso_huso=751886?img=best_any&amp;anim=flight&amp;pop=ol_751886#x415,y582,w1.7677">awesome zoomable tree of life</a> that you can find any species in and see how they relate to other species.</p>
<h3 id="stage-5">Stage 5</h3>
<p>I played way too much <a href="https://en.wikipedia.org/wiki/Desktop_Tower_Defense">Desktop Tower Defense</a> in middle school computer class which kickstarted my love for the tower defense genre so I knew I wanted to do my own spin on it for Stage 5 from the beginning.</p>
<p>It didn&rsquo;t take too long to come up with the idea of layering on a resource collection aspect to the tower defense mechanism, and then using your population (lives traditionally in TD) as a necessary resource for upgrading your towers on top of that.</p>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/q_auto:good/v1662402709/macrocosm/td_progress.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>The only notable challenge getting this stage set up initially was getting the pathfinding set up for multiple starting locations with different destinations, and then handling all the cases where you need to update (and block creating new towers when you&rsquo;re putting it on top of an enemy).  The <a href="https://www.redblobgames.com/pathfinding/">Red Blob Games articles on pathfinding</a> were helpful, particularly <a href="https://www.redblobgames.com/pathfinding/tower-defense/index.html">the one dedicated to tower defense</a>.</p>
<h3 id="stage-6">Stage 6</h3>
<p>Whew, onto stage 6 huh.  How many more stages do I have to do again? Only one more? Ok, we can get through this, even though it&rsquo;s already February 2019 and past when I originally thought I&rsquo;d be done with the whole game.  Oh well, time to make a mini civ game!</p>
<p>I&rsquo;m one of the millions of <a href="https://civilization.com/">Civilization</a> fans out there so it was a no brainer for me to base this stage off of it.  However I did know that creating a combat system with lots of units to design, and an enemy AI to fight against was going to be way too much for me to do for just this stage.  So I decided to focus on my favorite parts of civ like the exploration, city placement &amp; management, and tech upgrades.  This nicely danced around the tech intensive problems but also the design problem of &ldquo;what happens if you lose&rdquo; in a combat oriented game since I didn&rsquo;t want the player to ever have to restart.</p>
<p>The <a href="https://catlikecoding.com/unity/tutorials/hex-map/">Catlike Coding hex map tutorials</a> gave me a good boost on getting the initial map set up and world generation going but it still took a good bit of time to customize it and convert it from 3d to 2d.</p>
<p>It predictably took even more time to then write all the actual gameplay stuff on top of that like the first pass at city management, buildings, technology system, scouting, and the first pass at rivers and roads.  After 4 months I had this:</p>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/q_auto:good/v1662403697/macrocosm/settling_new_lands.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>Funnily enough, I got stuck on the triangle controls for city management and <a href="https://math.stackexchange.com/questions/3137017/how-do-you-calculate-the-weighting-of-a-point-inside-of-an-equilateral-triangle">asked this question</a> on the Mathematics stack exchange site and got a predictably dense math explanation that I couldn&rsquo;t follow and had to work it out myself.</p>
<h3 id="stage-7">Stage 7</h3>
<p>Alright, onto the grand finale of the last stage.  Something fun in a galaxy setting should be easy right? Here&rsquo;s what I was thinking of as the constraints for the stage:</p>
<p>Primary Constraints:</p>
<ul>
<li>Able to build it in reasonable amount of time</li>
<li>Tie in stage 6 technology research</li>
<li>End as a nice capstone for the game</li>
<li>Primary goal of exploring the galaxy</li>
</ul>
<p>Secondary Constraints:</p>
<ul>
<li>Use mass &amp; energy as resources</li>
<li>Have gameplay focusing on automating tasks that allow you to spread throughout the galaxy in an exponential fashion</li>
<li>Work between solar system &amp; galaxy scales</li>
<li>Potentially have multiple endings/strategies for replayability</li>
<li>Have nice space visuals so it&rsquo;s not all just UI manipulation</li>
</ul>
<p>Having come off a long Factorio play through not long before getting to this stage I naturally was caught by the bug to make an automation style game at this point.  I was imagining building up your colonies to mine resources, build probes and colony ships that would then colonize other nearby systems and repeat.</p>
<p>I wasn&rsquo;t quite sure how this would all fit together at first though so I started by building the part I was sure I wanted which was procedural galaxy generation and the 3 main zoom levels going between planet, system, and galaxy.</p>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/v1662404221/macrocosm/galaxy_zoom_2.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>Since the game is science based I wanted the galaxy generation to be grounded in reality even if there were liberties to be taken for the sake of gameplay.  I did a fair bit of research on all the nerdy galaxy formation stuff like: <a href="https://en.wikipedia.org/wiki/Stellar_classification#Harvard_spectral_classification">stellar classification</a>, <a href="https://en.wikipedia.org/wiki/List_of_planet_types">types of planets</a>, <a href="https://en.wikipedia.org/wiki/Sudarsky's_gas_giant_classification">gas giant classification</a>, <a href="https://en.wikipedia.org/wiki/Main_sequence">solar main sequence</a>, <a href="https://astronomy.stackexchange.com/questions/13165/what-is-the-frequency-distribution-for-luminosity-classes-in-the-milky-way-galax">distribution of luminosity classes</a>, <a href="http://www.projectrho.com/public_html/rocket/worldbuilding.php">some old world building site</a>, and a bunch of others.  I settled on using <a href="http://www.warehouse23.com/products/SJG31-1002">GURPS Space</a> which is a tabletop role playing guide as a starting point. It has a lot of details on galaxy, system, and planet formation with a bunch of probability tables that I could translate into a galaxy generation algorithm and then adapt as needed to fit the gameplay.</p>
<p>Once all that was in a decent state I had to come back to actually making a game out of it which turned out to be harder than I thought.  Prototype after prototype just kept feeling tedious, too complex, or just plain boring.  This turned out to be the trickiest game design problem of the whole game.  Just balancing all the constraints took so much trial and error to come up with a design I liked.</p>
<p>I ended up taking inspiration from <a href="http://old.dinopoloclub.com/minimetro/">Mini Metro</a> with the added layers of mining a small number of resources, combining them into other resources which are used to build and expand your galactic empire.  By focusing on a small number of resources each with a theme and then having the combinations follow what you might expect felt like it was pretty approachable and gave a good design space to work with.  For example Iron&rsquo;s theme is the base building resource, Xenon&rsquo;s is engines and transportation so naturally the combination of Iron and Xenon is the Thrusters resource used by colony and transport ships.</p>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/v1662404314/macrocosm/stage_7_update.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>Once I got Stage 7 in a reasonable state it was time to go back to the beginning and start making content, work on the final art, and just refine everything.  And oh yeah, by the way, it&rsquo;s already a year and half in and the game is nowhere near being done :)</p>
<h2 id="a-brief-interlude-on-tools">A Brief Interlude on Tools</h2>
<p>Roughly at this point in the project (really mixed in throughout working on other stuff) I spent a good chunk of timing honing the tools of the project.  As a solo dev you don&rsquo;t want to spend too much time just working on tools but being careful with creating custom tools for your project and using the right 3rd party assets really is worth it&rsquo;s time in weeks.  I would also advise to be pretty picky about what 3rd party assets you choose to use because integrating them into your project, really learning how they work deeply, and even finding some bugs with them can really sap your time.</p>
<p>One last tidbit of advice is to try to research and decide on core assets and libraries as early as you can to save yourself the hassle of switching to different ones mid or late project.  Graphics, object pooling, and tweening packages are all good examples of this.</p>
<p>Here are the notable ones I used with some notes.</p>
<ul>
<li>I created my own <a href="/unity-save-game-editor/">Save Editor</a> that was worth its weight in gold to be able to manipulate my save file outside of code to test all sorts of different scenarios and flip between different save files that were in different states and see what was going on.</li>
<li>Around this time I switched from <a href="http://svgimporter.com/">SVG Importer</a> to the Unity <a href="https://forum.unity.com/threads/vector-graphics-preview-package.529845/">vector graphics package</a> to render all the games graphics.  SVG Importer wasn&rsquo;t being supported anymore, had some memory leaks, and required different components depending on what you were doing.  It took a bit to get everything switched over but I&rsquo;m happy I did it.  The vector graphics package is only around 3/4ths baked and then put on hold as most things are from Unity these days but I was able to get around its limitations.  Having 9-sliced svg images would be real nice though.</li>
<li>I used <a href="http://strangeioc.github.io/strangeioc/exec.html">StrangeIOC</a> from the beginning of the project since I had used it on my previous project. It&rsquo;s nice dependency injection that really helps architect your project in a clean way.  The caveats are that I made a couple small modifications to it, there are a couple quirks with how to use it, and don&rsquo;t use the mediator concept (just bind everything in views directly) but being able to inject data models, events, and services into components is reaaally nice.</li>
<li><a href="https://nice-vibrations.moremountains.com/">Nice Vibrations</a> is a great and easy to use package that does exactly what it says on the tin.</li>
<li>The <a href="https://assetstore.unity.com/packages/tools/utilities/color-palette-32189">Color Palette</a> asset is decent and saved me some trouble but it&rsquo;s nothing too fancy that someone who knows how to create custom editor windows couldn&rsquo;t do without too much trouble.</li>
<li>I got <a href="https://assetstore.unity.com/packages/tools/gui/ui-flexbox-124071">Unity Flexbox</a> to try to get something that could get me off of the Horizontal and Vertical layout groups which never do what you want and I despise.  However, the flexbox asset had some issues of its own I can&rsquo;t remember and I had to modify it anyways and didn&rsquo;t use it everywhere.  5/10</li>
<li>I started off by integrating the <a href="https://unitytech.github.io/clouddiagnostics/userreporting/UnityCloudDiagnosticsUserReports.html">Unity User Reporting</a> package that integrates with their cloud service but their site kept being super slow for me, sometimes would just be down when trying to send it a new report (or very slow as well), showed the screenshot in the wrong aspect ratio,  and their retention period is a woefully short 7 days.  All of this greatly frustrated the web developer in me and so I just wrote my own.  It took me like 3 days to get everything I used from Unity working in my own except it was super fast and had infinite retention.  Anyways, I <strong>highly</strong> recommend having a system in your game to both capture exceptions and report them to a server as well as an in game way for players to send feedback.  This was so valuable to have both for debugging issues (and being able to download the save file that caused them) as well as improve the game through player feedback.  If you&rsquo;re interested in any of this, send me a message. I should probably turn that into its own post and open source it.</li>
<li><a href="https://github.com/UnityPatterns/ObjectPool">Object pool</a> simple and effective.  Tweaked a very small amount.</li>
<li>Switched from iTween to <a href="http://dentedpixel.com/developer-diary/leantween-speed-comparison-to-itween/">LeanTween</a> for performance.</li>
<li><a href="https://acegikmo.com/shapes/">Shapes</a> is a great asset for any sort of dynamic vector shape rendering.  I use it a bunch for lines, progress bars, circles, donuts, and arcs that are all nicely anti-aliased.</li>
</ul>
<h1 id="mid-project">Mid Project</h1>
<p>So at this point in the project all 7 stages have most of their basic systems and gameplay worked out, the tools and infrastructure of the game are pretty close, but it&rsquo;s all developer art still and everything needs this tricky thing called content.  I assume everyone who&rsquo;s made a single-player game before knows how time consuming this actually is, but for a new solo dev I found out it&rsquo;s really easy to grossly underestimate.</p>
<p>The trap I fell into was thinking that once the systems were done content was a matter of adding rules, data, and a few more scripts.  In reality though it&rsquo;s doing those things plus then the realization you need a little more variety so the gameplay systems need extending, then play testing, then not being satisfied and tweaking some more, then working through problems with integrating the art, then balancing all the rules and systems, all while fixing a bunch of bugs.</p>
<p>Speaking of art though, it&rsquo;s an amazing feeling to see the first final (or near final) art in the game.  The project just seems so much more real after adding it and it&rsquo;s a great morale boost.  I highly recommend finding an artist to hire if you&rsquo;re not sure you can pull it off yourself.  The look of your game really helps differentiate you amongst the thousands of other games out there to choose from.  Being able to create the first versions of the art to experiment and prototype with is really important as well though.  It&rsquo;ll save you time and money trying different ideas with placeholder cheap assets before committing to creating the final versions.</p>
<img alt="Stage 1 Before and After" title="Stage 1 Before and After" src="https://res.cloudinary.com/dillonshook/image/upload/v1662404470/macrocosm/stage_1_before_after.png" >
<p>One thing you might also notice in that before and after is that the big number score got removed.  I got so far as to implement it in each of the stages with a nice animation ticker effect whenever you got more points, but that ticker effect combined with getting points all the time meant that the score was allocating memory like crazy constructing new strings constantly.  I was starting to look into ways around that when I thought about it more and wasn&rsquo;t really happy with the arbitrary nature of what gave you more score and how much.  So I did the simplest thing and just removed it all which I&rsquo;m happy with.  An important skill, especially as a solo dev, is the ability to evaluate your ideas and get rid of the ones that aren&rsquo;t working. Every single idea you have isn&rsquo;t a good one, trust me on that :)</p>
<p>Another example of an idea that needed editing was making the content for the third stage which is the cellular scale with a rhythm tapping and sliding to music theme. I started out by thinking I could randomly or procedurally create all the cells to tap on while each song is playing to save me from having to manually create a beatmap ahead of time.  The more I playtested that though the more I realized that procedural wasn&rsquo;t going to work for the amount of time it would take to make it good.  There needed to be predicable repeating patterns of cells to tap just like how in a song there&rsquo;s repeating patterns that make up the structure of the song (intro, verse, chorus, etc).  If the player fails a song by missing too many beats it should also be the same patterns so you can actually practice getting better.  So after the realization that doing that procedurally wasn&rsquo;t easy I just made a simple system that allowed me to create the beatmaps, and then it was just a matter of spending the time to create and test all of them.</p>
<p>There were plenty of other challenges and things to learn along the way while refining each stage and adding content.  On the creature stage I had to figure out how to animate the dino&rsquo;s and promptly ran into the limitation of the vector graphics package that doesn&rsquo;t really work with any of Unity&rsquo;s other sprite animation systems.  After doing some research on options I landed on using <a href="https://redraininthenight.wixsite.com/viviart/anima2d">Anima2D</a> for its bones and IK support even though it doesn&rsquo;t seem to be supported or maintained anymore.</p>
<video controls loop="true">
  <source src="https://res.cloudinary.com/dillonshook/video/upload/v1662404882/macrocosm/creature_anim_clip_smaller.mp4" type="video/mp4">
  Your browser does not support the video tag.
</video>
<p>Adding support for the notches at the top of phone screens was another unexpected challenge.  Luckily there&rsquo;s a nice <a href="https://github.com/5argon/NotchSolution">Notch Solution</a> out there that does most of the hard work.  Making sure that all the game UI worked with different aspect ratios and while compressed in with the notches took a lot of bug fixing though.</p>
<p>A good forcing function for keeping the game simple while designing it was adding tutorials for each stage.  It became pretty obvious that if I couldn&rsquo;t explain what to do in a few pop up tutorials then I had made the game too complicated.  This mainly was an issue on the last two stages building a civilization and a space empire which required a bit of simplification so I could try to explain to the player how they worked.  In general though the tutorial system was yet another system that turned out to be more complicated than I originally thought it would need to be.  It needs to handle all sorts of events from the game to trigger tutorials, pausing and unpausing the game correctly (don&rsquo;t want to unpause the game if it&rsquo;s already paused while a tutorial is shown), handling multiple tutorials trying to open at once, as well as saving and restoring the tutorial state when the game quits and then reopens.</p>
<p>The last pain in the butt around this time in development was getting automatic builds created every day for both iOS and Android.  This was really helpful to be able to test on actual devices and catch bugs that were device specific as soon as possible (usually ones with touch input handling).  I did end up paying for <a href="https://unity.com/products/cloud-build">Unity Cloud Build</a> since it&rsquo;s obviously made for this exact thing.  The build process for mobile apps is way more complicated to set up on your own computer than it should be so you&rsquo;re kinda forced into using a cloud solution.  But even with the hosted solution it&rsquo;s not a walk in the park.  You have to create custom scripts around accessing the build version inside the game and uploading the builds to the app store.  Just setting up the certificates for iOS is <a href="https://docs.unity3d.com/Manual/UnityCloudBuildiOS.html">an absurd back and forth process</a>.</p>
<h1 id="finish-it">Finish it</h1>
<p>At this point in the project I was feeling like you&rsquo;re probably feeling reading this: &ldquo;This took way longer I thought and I&rsquo;m ready to be done with it&rdquo;  :)</p>
<p>But as with any passion project it&rsquo;s hard to release something when you know it could be better and you see all the little flaws.  So on goes the polishing, optimizing, tweaking, balancing, play testing, iterating, and bug fixing.  I&rsquo;m curious to know if anyone&rsquo;s tried to quantify development time as a function of game length is but I&rsquo;d be willing to bet it&rsquo;s more than linear.  Full playthroughs take longer and there&rsquo;s just more of everything to test and tweak like you can see here in the before and after of the city management UI.</p>
<img alt="Stage 6 Before and After" title="Stage 6 Before and After" src="https://res.cloudinary.com/dillonshook/image/upload/v1662405314/macrocosm/Stage_6_Before_After.jpg" >
<p>More code also means more places that can be slow which is important to pay attention to on mobile devices.  This is another section that could be a whole post of its own but here&rsquo;s my couple Unity optimization tips:</p>
<ul>
<li>Get really familiar with the built in profiler, and add lots of custom samples with <code>UnityEngine.Profiling.Profiler.BeginSample(&quot;My Sample&quot;);</code></li>
<li>Hunt down all the things that are allocating memory every frame. The usual suspects are creating strings, starting coroutines, and dictionaries keyed by enums need equality comparers.</li>
<li>Know your data structures, use HashSet&rsquo;s instead of array&rsquo;s if you&rsquo;re mainly doing contains checks.</li>
</ul>
<p>Getting the word about about your game is tough like you might imagine.  I&rsquo;m only barely qualified to talk about it myself so instead of me writing lots more words I&rsquo;ll point you in the direction of <a href="https://howtomarketagame.com/blog/">Chris Zukowski&rsquo;s how to market a game blog</a> that has lots of useful info.  Marketing for mobile games does seem to be a different beast though, some of which seems to involve questionable ethics that I don&rsquo;t want any part of.  What you should be part of instead is your local game developer community! It&rsquo;s super helpful to find your peers and participate helping others with their struggles and in turn getting help with your own struggles.  There were several talks given by the local group here that opened my eyes to things I hadn&rsquo;t even thought about before.</p>
<p>If you are into marketing and interested in working on commission though definitely feel free to contact me through the email in the sidebar.</p>
<p>So at some point you just need to pick a date to release the damn thing.  I gave myself the deadline of releasing the week of <a href="https://www.paxsite.com/">Pax East</a> so I could do some guerilla marketing while I was there.  I&rsquo;m glad I picked the date and got it out there but in hindsight it was a little crazy to do with going to the conference then flying across the country to see family the same day.  If you can time it to give yourself a bit of free time after releasing to fix the bugs, do all the social things, and monitor all the other things that would be wise.</p>
<p>The only real hiccup with releasing was the <a href="https://developer.android.com/distribute/console">Google Play Console</a> having some really awful and confusing UX.  There&rsquo;s no way to promote your pre-sale listing to full sale. You have to duplicate the whole store listing.  The button that is labeled &ldquo;Release to Production&rdquo; <em>actually</em> means &ldquo;Send the build to our review team that will take 48 hours to review before you can release unless you already did that by turning on this other setting we won&rsquo;t tell you about&rdquo;.  So yeah, Android release was delayed&hellip;</p>
<p>But in the end it was released and it felt great to finally get it out there after working on it for so long!</p>
<h1 id="final-lessons-learned-and-advice-for-aspiring-game-developers">Final Lessons Learned and Advice for Aspiring Game Developers</h1>
<p>Everyone says this, but I&rsquo;ll say it again &lsquo;cause it&rsquo;s true: Start on a small project that&rsquo;s realistic to finish and build up to your bigger dream game you want to make.  This was my 3rd serious attempt at making a game, but the first time I actually released one.  Even still, I let my ambitions and scope go wild that made everything take way longer and could have easily led to abandoning it.  There&rsquo;s so many things you can&rsquo;t possibly know when you&rsquo;re making your first game so starting small and going start to finish will give you the experience to use in the next project.</p>
<p>The games industry is a very competitive space and there&rsquo;s a high likelihood you won&rsquo;t even make minimum wage for the time you put into it on your first game.  I certainly haven&rsquo;t yet.  Try to find what your passion is for making games and what you want to accomplish.  If you&rsquo;re just in it for the money, there are easier ways to make money for your time. I&rsquo;d advise seeing how far you can get doing game development as a side project to make sure you love doing it and can stick with it before pursuing it as a full time job.</p>
<p>If you can find people to partner with to bring a game to life that would be awesome! I think it can be a bit of a double edged sword though.  You&rsquo;ll have to find people that are really on the same page for what the game should be, with complimentary skills, and are realistic about committing to finishing the game given how long it&rsquo;ll take to make.  If the collaboration doesn&rsquo;t work out it can easily kill the project.</p>
<p>When you&rsquo;re first thinking about what game you want to make, write all your cool game ideas first.  I know you have at least a couple.  Then pitch the idea to your friends and family and get their reactions.  Do they get the idea? Are they confused? Does it sound like fun?  This should help you both refine the idea and get a sense of what it&rsquo;ll be like when it comes time to promote the game.  What&rsquo;s <a href="https://youtu.be/l3QnovWYvwo?t=245">the hook, and what&rsquo;s the kicker?</a> Remember that you could be working on this for quite awhile so really make sure this is the right idea that you&rsquo;re super excited about.  It&rsquo;s a big commitment!  When you&rsquo;re in the thick of development it&rsquo;s hard to push through all the non fun stuff like fixing bugs or just getting the sound to play correctly so you need the spark of the idea to keep you going!</p>
<p>Finally, don&rsquo;t stop dreaming and trying.  I know it can all get a little overwhelming both in learning what it takes and actually doing it, but you learn so much along the way.  The journey is more important than the destination</p>
<p><br/><br/>
There&rsquo;s lots of interesting discussion on this post over on Hacker News too!
<a href="https://news.ycombinator.com/item?id=32851196">https://news.ycombinator.com/item?id=32851196</a></p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>What language am I writing again?</title>
      <description>Have you ever had that brainfart moment when you&amp;rsquo;re writing code and forget what syntax you&amp;rsquo;re supposed to be using? An example is helpful:
This is from a recent project that used React + Typescript + GraphQL + Styled Components which is a prime combo for this sort of situation. At bare minimum we have these languages all represented in the same file.
Typescript GraphQL CSS React Depending on how you&amp;rsquo;re counting, you could also add in Javascript (since you need to know it anyways for Typescript), HTML (since the React syntax is built on it), and even the interpolated strings like ${address.</description>
      <link>https://dillonshook.com/what-language-am-i-writing-again/</link>
      <pubDate>Wed, 23 Mar 2022 16:00:00 +0000</pubDate>
      
      <guid>https://dillonshook.com/what-language-am-i-writing-again/</guid>
      
      <content:encoded><![CDATA[<p>Have you ever had that brainfart moment when you&rsquo;re writing code and forget what syntax you&rsquo;re supposed to be using?  An example is helpful:</p>
<script src="https://gist.github.com/dshook/5268e8a9c6affa0416ba4c3c2a9c1740.js"></script>
<p>This is from a recent project that used React + Typescript + GraphQL + Styled Components which is a prime combo for this sort of situation.  At bare minimum we have these languages all represented in the same file.</p>
<ul>
<li>Typescript</li>
<li>GraphQL</li>
<li>CSS</li>
<li>React</li>
</ul>
<p>Depending on how you&rsquo;re counting, you could also add in Javascript (since you need to know it anyways for Typescript), HTML (since the React syntax is built on it), and even the interpolated strings like <code>${address.street} ${address.city}, ${address.state}</code> which have a special syntax on their own.</p>
<p>This isn&rsquo;t particularly a new trend. Perhaps you&rsquo;ve had the &lsquo;fortune?&rsquo; in your career to remember code like this that used to be a norm.</p>
<script src="https://gist.github.com/dshook/f3dfe229b666b8d3b48d91d2dd0eec04.js"></script>
<p>I found this example on GitHub and stripped it down a lot for brevity and sanities sake, but even still it gives me flashbacks of seeing 4000 line PHP files that mix and match PHP, SQL, HTML, Javascript, and CSS all in one big convoluted mess.</p>
<p>You might be asking yourself &ldquo;ok there&rsquo;s a lot of languages, is that always bad though?&rdquo;.  One thought experiment I like to test ideas against is taking them to the extreme and see where it goes.  For this example, the extremes are:</p>
<ul>
<li>The file only has one language</li>
<li>The file has 10 (20?, 30?, 100?) different languages in it.  You switch between them practically on a character by character basis.</li>
</ul>
<p>Straight away I know which extreme I want to aim towards. I&rsquo;m pragmatist so I&rsquo;m not saying that files must only ever contain a single language, but the exercise gets you thinking about the ideal and where the breaking point is.</p>
<p>There&rsquo;s some notable disadvantages of having many languages in a file, and particularly embedding languages within other languages like the GQL and CSS inside the strings in the first example.</p>
<ol>
<li>More cognitive overhead</li>
<li>Editor tooling is harder and slower</li>
<li>Single responsibility principle suffers heavily</li>
</ol>
<p>There have been some interesting experiments with <a href="https://tratt.net/laurie/essays/entries/fine_grained_language_composition.html">composing languages together at runtime</a>.  I agree with the author&rsquo;s plight that switching languages is all too hard and we end up getting locked into our choices, however I don&rsquo;t agree that their solution of composing languages together as they did is the right one.  As mentioned in the conclusion, there&rsquo;s a lot of friction switching languages.  A lot of weird edge cases.  A lot of, for lack of a better term, &ldquo;magic&rdquo;.  Well, maybe there is a better term: complexity.  Since our prime directive as software engineers is to manage complexity and keep it at a minimum we have to keep looking for other solutions.</p>
<p>As mentioned before, I&rsquo;m not about to go preaching to the world that you should only have one language per file, just that your eyebrows should instinctively move up when you see this happening.  Design is all about balancing trade-offs, and this is no exception.</p>
<p>Have you&rsquo;ve seen some egregious examples in your career?  Perhaps a bunch of <a href="https://en.wikipedia.org/wiki/Domain-specific_language">DSL</a>&rsquo;s mixed in?  Share them in the comments!</p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>How Hard is your Email to Say?</title>
      <description>You&amp;rsquo;re at the doctors office, talking to an aquaintence, or ordering something on the phone and they ask the question: What&amp;rsquo;s your email? Depending on your name, age, and your life choices this can be a breeze or the dreaded question. How long does it take before you have to break out the phonetic alphabet? How many times do you have to repeat it?
Today we&amp;rsquo;re going to come up with a scoring system to measure how painful your email is to tell someone.</description>
      <link>https://dillonshook.com/how-hard-is-your-email-to-say/</link>
      <pubDate>Sat, 01 Aug 2020 23:31:58 +0000</pubDate>
      
      <guid>https://dillonshook.com/how-hard-is-your-email-to-say/</guid>
      
      <content:encoded><![CDATA[<p>You&rsquo;re at the doctors office, talking to an aquaintence, or ordering something on the phone and they ask the question:  What&rsquo;s your email?  Depending on your name, age, and your life choices this can be a breeze or the dreaded question.  How long does it take before you have to break out the <a href="https://en.wikipedia.org/wiki/NATO_phonetic_alphabet">phonetic alphabet</a>?  How many times do you have to repeat it?</p>
<p>Today we&rsquo;re going to come up with a scoring system to measure how painful your email is to tell someone.  It&rsquo;s a golf scoring system with low scores being easy and each point is one unit of struggle for both you and the recipient.</p>
<p>Lets start with the hypothetical perfect example: <code>bob@gmail.com</code> coming in with a score of 0.  It&rsquo;s short, contains only a common name, and is on a popular mail host.  From there, lets see all the ways yours is harder to say.</p>
<h3 id="non-popular-email-domain">Non popular email domain</h3>
<p>If&rsquo;s it&rsquo;s not <code>@gmail.com</code>, <code>@outlook.com</code>, <code>@hotmail.com</code>, <code>@yahoo.com</code>, <code>@icloud.com</code>, or <code>@aol.com</code> take a point.  These are services people are familiar with and don&rsquo;t have to think about typing.  They just might judge you on which on you use.</p>
<h3 id="domain-with-subdomain">Domain with subdomain</h3>
<p>If you have an email from school hanging around you&rsquo;re likely to be guilty of this one like I am.  My school address got the <code>alumni</code> subdomain added to it.  I&rsquo;ve seen a bunch of other emails from schools with the department included <code>@cs.xyz.edu</code>, or other fun stuff just &lsquo;cause like <code>@mail.xyz.edu</code>. Add a point for each level of domain past the normal two levels.</p>
<h3 id="non-common-top-level-domain">Non common top level domain</h3>
<p>If you&rsquo;re extra special you&rsquo;ll have a TLD like <code>.is</code>, <code>.io</code>, <code>.rocks</code> or some other novelty.  Take a point if it&rsquo;s not <code>.com</code>, <code>.org</code>, <code>.net</code>, <code>.edu</code>, or <code>.gov</code>.</p>
<h3 id="long-username">Long Username</h3>
<p>After a certain amount of letters in the username you&rsquo;re just going to have to slow down and repeat that.  +1 point for every 2 characters over 6.  <code>bobsondugnutt@gmail.com</code> gets a 3 for example.</p>
<h3 id="punctuation">Punctuation</h3>
<p>Periods are pretty common in email usernames because we&rsquo;re used to saying &ldquo;dot&rdquo; for the domain anyways but they&rsquo;ll still slow down whoever is typing it and likely miss what comes after it.  Any other punctuation is a great typo opportunity.  +1 for any period in the username, +2 for any other punctuation in the username <em>or</em> domain.</p>
<h3 id="numbers">Numbers</h3>
<p>Just like punctuation, numbers are in that more unfamiliar part of the keyboard where fingers get lost and push the wrong buttons.  +1 for each number.</p>
<h3 id="any-hard-to-say-words-that-arent-your-name">Any hard to say words that aren&rsquo;t your name</h3>
<p>Bit of a loose definition here but you should know it when you see it.  If you have a username that&rsquo;s not your name (where they might be able to refernce spelling) and is not a common and easily spellable word take a point. <code>xThreadRiPPerX@gmail.com</code> I&rsquo;m talking about you whoever you are.</p>
<h3 id="similar-sounding-letters">Similar sounding letters</h3>
<p>This is one I know all too well from one of my emails where I have to break out the phonetics after the 3<sup>rd</sup> repetition and they&rsquo;re still confused. &ldquo;mn&rdquo;? &ldquo;mm&rdquo;? &ldquo;nm&rdquo;?  Add +1 for each pair of similar sounding letters that aren&rsquo;t in an easily pronouncable and spellable word.  Some common pairs include: BV, DG, GZ, MN, JK</p>
<h3 id="bonus-unicode--emoji">Bonus: Unicode &amp; Emoji</h3>
<p>Good luck getting someone to type that over the phone. +5 for each unicode character.</p>
<div>
   &nbsp;
</div>
<p>The email I&rsquo;ve been using the most over the past few years is guilty of a few of these and comes in at a score of 3 so I&rsquo;m well aware of the struggle.</p>
<p>Let me know in the comments what score yours is and the struggles you&rsquo;ve had!</p>
<p>You can also check out some of the discussion over on Hacker News
<a href="https://news.ycombinator.com/item?id=27454467">https://news.ycombinator.com/item?id=27454467</a></p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>Your Reach is More Than You Think</title>
      <description>On analytics, influence, and creativity</description>
      <link>https://dillonshook.com/your-reach-is-more-than-you-think/</link>
      <pubDate>Tue, 30 Jun 2020 04:15:00 +0000</pubDate>
      
      <guid>https://dillonshook.com/your-reach-is-more-than-you-think/</guid>
      
      <content:encoded><![CDATA[<p>I&rsquo;ve been in the process of moving over to a new site analytics provider and when my previous couple of posts got way more traffic than I was expecting it was pretty interesting to compare the numbers between them.  I&rsquo;m going to avoid naming them but you should be able to figure them out without much trouble.  Analytics provider &ldquo;A&rdquo; is a fairly typical one, gets blocked by most adblockers, and respects the <a href="https://en.wikipedia.org/wiki/Do_Not_Track">do not track</a> browser setting even though no personally identifiable info is being stored or tracked across sessions.  Analytics provider &ldquo;B&rdquo; is a lightweight alternative that doesn&rsquo;t get blocked by most adblockers and doesn&rsquo;t look at the DNT setting as far as I know.</p>
<p>After seeing around a 25% - 35% increase in traffic just from looking at a different analytics provider it got me thinking about all the other ways you might underestimate your reach!</p>
<p>Taking the analytics one step further I decided to look at the server logs and see how many more hits I was getting from the people who were able to block analytics B (or who have javascript disabled altogether).  Again I saw around a 30% increase in traffic, putting the total increase around 65% over the &ldquo;naive&rdquo; number!</p>
<p>These multiplicative effects are all over the place when you create content.  Comparing the number of upvotes the posts got on <a href="https://news.ycombinator.com/">Hacker News</a> to how many views they got is sitting around a 95x multiplier.  Comparing reputation on <a href="https://stackoverflow.com/users?9&amp;tab=reputation&amp;filter=all">Stack Overflow</a> to the number of people reached that can be found on users pages (across a wide spectrum of reputations) is around an 80x multiplier.  Comparing <a href="https://github.com/">GitHub</a> stars to <a href="https://www.npmjs.com/">NPM</a> weekly downloads is a crazy 800+ multiplier.  I&rsquo;d love to get more examples from other sites like social networks but they keep their viewership data under lock and key for the most part.  I bet they have some big multipliers too though.</p>
<p>Even past the single view or consumption of something you create, think of all the other ways you have more reach.  In audio and visual mediums there could be multiple people consuming from one hit.  How many times has it been shared in conversation?  Have you ever influenced a decision or someone else&rsquo;s creativity based on what you create?</p>
<p>The existence of the <a href="https://en.wikipedia.org/wiki/1%25_rule_(Internet_culture)">1% rule</a> also means that the competition for being an
<em class="bounce-text"><span>I </span><span>N </span><span>F </span><span>L </span><span>U </span><span>E </span><span>N </span><span>C </span><span>E </span><span>R</span></em> is so tiny compared to the potential audience you have when you create content and put yourself out there.</p>
<p>So to wrap it up, there&rsquo;s so many ways you have an impact beyond the likes, upvotes, pageviews, stars, and other things we like to focus on and I wholeheartedly encourage you to get out there and make the world a better place through your creations.</p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>A Critique of React Hooks Addendum</title>
      <description>A follow up to A Critique of React Hooks including quiz results and more</description>
      <link>https://dillonshook.com/a-critique-of-react-hooks-addendum/</link>
      <pubDate>Thu, 07 May 2020 17:16:00 +0000</pubDate>
      
      <guid>https://dillonshook.com/a-critique-of-react-hooks-addendum/</guid>
      
      <content:encoded><![CDATA[<p>This is a follow up to <a href="/a-critique-of-react-hooks/">A Critique of React Hooks</a> which blew up beyond expectations and generated a ton of discussion that I enjoyed reading.  If you haven&rsquo;t read it yet please take the detour before spoiling the quiz for yourself!  I&rsquo;m not one to harp on a subject repeatedly but after seeing all the quiz results come in I thought it would be valuable to share them as well as a couple of thoughts across common themes in the discussion.</p>
<h2 id="quiz-results">Quiz Results</h2>
<p>With over 2,600 responses at the time of writing this is the biggest sample size of an unscientific quiz I&rsquo;ve ever given!  Even ignoring the last question which was mostly there for laughs, the results were still very interesting. The median score is 1 out of 4 questions despite 3/4<sup>ths</sup> of the questions having a correct popular vote. To me this indicates that people are familiar with a few of the hooks but haven&rsquo;t used all the ones included in the quiz.</p>
<p><img src="images/Screenshot-2020-05-03-17.19.35.png" alt="Screenshot-2020-05-03-17.19.35"></p>
<h3 id="quesiton-1">Quesiton 1</h3>
<p><img src="images/Screenshot-2020-04-13-09.15.08.png" alt="Question 1 code"></p>
<p><img src="images/Screenshot-2020-05-03-17.20.05.png" alt="Question 1 results"></p>
<p>The first and simplest question had the highest percentage of correct answers to no surprise.  However, over 40% of you were surprised that the <code>Child</code>&rsquo;s <code>useEffect</code> fires before the <code>Parent</code>&rsquo;s after rendering both components.</p>
<h3 id="question-2">Question 2</h3>
<p><img src="images/Screenshot-2020-04-13-13.44.02.png" alt="Question 2 code">
<img src="images/Screenshot-2020-05-03-17.20.35.png" alt="Question 2 results"></p>
<p>This one surprised me when I first ran it but makes sense when you take a minute to think about it. Both <code>useLayoutEffect</code>&rsquo;s run before the <code>useEffect</code>&rsquo;s and they run from the bottom of the component tree upwards. Hopefully you&rsquo;ll never get into a situation where this particular ordering matters though since only 30% were able to predict the order at first glance.</p>
<h3 id="question-3">Question 3</h3>
<p><img src="images/Screenshot-2020-04-13-12.47.20.png" alt="Question 3 code">
<img src="images/Screenshot-2020-05-03-17.21.00.png" alt="Question 3 results"></p>
<p>This question has the intentional bug of creating a new object every rerender and passing it to the child as a prop which causes the memo to run every rerender which a third of you caught. It would be interesting to compare these results with a question around a similar bug of creating a function inside the render and passing it to the child which causes the Child to rerender every time the Parent does:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">function</span> Parent(props) {
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">function</span> doSomething() {
</span></span><span style="display:flex;"><span>    <span style="color:#998;font-style:italic">// … props …
</span></span></span><span style="display:flex;"><span><span style="color:#998;font-style:italic"></span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">return</span> &lt;<span style="color:#000080">Child</span> <span style="color:#008080">onSomething</span><span style="color:#000;font-weight:bold">=</span>{doSomething} /&gt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is fixed with a <code>useCallback</code> hook, and the quiz problem is fixed by either updating the object via <code>useRef</code> or refactoring the props of the child to not take an object as a prop.  Both of these scenarios can come from refactoring a class component where you either have a class method or property that don&rsquo;t get recreated every render.  When converting you have to keep this in mind and take the obfuscation penalty wrapping them with hooks.</p>
<h3 id="question-4">Question 4</h3>
<p><img src="images/Screenshot-2020-04-13-13.14.43.png" alt="Question 4 code">
<img src="images/Screenshot-2020-05-03-17.21.15.png" alt="Question 4 results"></p>
<p>This is the opposite sort of bug where a <code>Child</code> component needs to be rerendered but isn&rsquo;t.  It happens when <code>useRef</code> is needed to maintain a stable reference over rerenders but then a child component is added that needs the value.  If the whole ref is passed as a prop instead of the <code>.current</code> value the <code>Child</code> component won&rsquo;t be rerendered because the ref hasn&rsquo;t changed.  In isolation this shouldn&rsquo;t be too hard to spot but can be tricky when there are many props being passed from different places to a component.</p>
<h2 id="a-few-more-thoughts">A few more thoughts</h2>
<h3 id="on-more-stuff-to-learn">On More Stuff to Learn</h3>
<p>There were a few comments about how easy hooks are to learn. While I would agree that reading the docs and getting the gist of how hooks work doesn&rsquo;t take too long, fully understanding their ramifications, and the bugs you&rsquo;ll run into, will be an ongoing process. The quiz results show that even if you got everything right, you&rsquo;ll likely be working with someone that didn&rsquo;t.</p>
<p>Adding more to learn just raises the barrier to entry.  And every developer should be trying to remove as many barriers to entry <a href="https://www.youtube.com/watch?v=rvskMHn0sqQ">because it&rsquo;s in our best interest to do so</a>.</p>
<h3 id="they-dont-compose-with-themselves">They Don&rsquo;t Compose with Themselves</h3>
<p>Programming languages are powerful because they give you building blocks that you can compose together in many different ways. If you have the blocks <code>loop</code> and <code>function</code>, all the possible combinations are valid including nesting. <code>loop</code> in <code>loop</code>, <code>loop</code> in <code>function</code>, <code>function</code> in <code>loop</code>, etc. Hooks don&rsquo;t have this property. You can&rsquo;t use a hook inside a hook (excluding custom hooks that are just wrapper functions for the native hooks). You can&rsquo;t use a hook in a loop. So when you go from having one thing handled with <code>useRef</code> to needing multiple things handled by <code>useRef</code> you can&rsquo;t just stick it in a loop or array like you would with any other language building block. You <a href="https://github.com/facebook/react/issues/14072">have to redesign it to work with hooks</a>.</p>
<h3 id="why-control-flow-matters">Why Control Flow Matters</h3>
<p>When you inevitably get to debugging, you should start with a mental model of what you expect to happen including all the assumptions about what state you&rsquo;ll be in throughout the process.  Whenever you step or log your way through and one of your assumptions is broken, you know you&rsquo;ve found a piece of the bug.  Now you just have to figure out why your assumption was wrong.</p>
<p>Between the hook execution order and figuring out why a component rerendered it can be very difficult to create that mental model of what&rsquo;s supposed to happen.</p>
<h2 id="last-words">Last Words</h2>
<p>Whether you like hooks or not, I hope through these posts you&rsquo;ve learned something even if it isn&rsquo;t as general as I&rsquo;d like :)</p>
<p>As always,
Thanks for reading and discussing!</p>
<p><br/><br/>
Lots of comments also posted over on Hacker News <a href="https://news.ycombinator.com/item?id=23102639">https://news.ycombinator.com/item?id=23102639</a></p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>A Critique of React Hooks</title>
      <description>A few thoughts to consider when using React hooks</description>
      <link>https://dillonshook.com/a-critique-of-react-hooks/</link>
      <pubDate>Mon, 27 Apr 2020 18:04:00 +0000</pubDate>
      
      <guid>https://dillonshook.com/a-critique-of-react-hooks/</guid>
      
      <media:content url="/a-critique-of-react-hooks/cover_hu5e1aaabc640630ddd244fc7c4579e76e_139989_720x0_resize_q85_h2_box.webp" medium="image" />
      
      <content:encoded><![CDATA[<p>I want to preface this critique by saying I think that hooks are not all bad. If I were starting a new react project today I would still use them despite all these flaws. However, that doesn&rsquo;t make them immune to criticism. Given their design, I think there are a number of factors limiting their longevity and I won&rsquo;t be surprised if Facebook comes out with The Next Greatest Thing™ to address some of their shortcomings.</p>
<h2 id="1-more-stuff-to-learn">1. More Stuff to Learn</h2>
<p>The first and easiest gripe with hooks is simply that they&rsquo;re another thing to learn. I was fortunate to get into frontend development at a simpler time where you could just dump <a href="https://jquery.com/">jQuá</a> on the page and call it a day. Then as new complications came along I could learn them incrementally. Nowadays there is just <em><a href="https://roadmap.sh/frontend">so       much       to       learn</a></em>. If I was a new developer and saw that chart I would want to go home and rethink my life.</p>
<p>Learning new things is good though! We should be learning all the time! The problem with learning about hooks is that they&rsquo;re not generally applicable knowledge about computing or programming. They&rsquo;re a React-specific thing. In 5-10 years when you&rsquo;re programming something completely different your knowledge of hooks will be completely obsolete. Whenever possible, you should pick learning about generally applicable thing A over very specific thing B so that your knowledge can compound and pay dividends.</p>
<h2 id="2-they-dont-interoperate-with-class-components">2. They Don&rsquo;t Interoperate With Class Components</h2>
<p>If you&rsquo;re starting a new project that doesn&rsquo;t use class components you can <code>GOTO 4</code>, but read on if you&rsquo;re in the camp of working on an app from the time before hooks.</p>
<p>Say you&rsquo;re working in a project that&rsquo;s been around for a while and it&rsquo;s 50% <code>FunctionComponent</code>&rsquo;s and 50% class components. Now it&rsquo;s time to implement a new feature which you think would be a perfect use of a custom hook to do the heavy lifting. This works great in the original <code>FunctionComponent</code> that uses it, but now you need to use that feature in a class component. What do you do?</p>
<p>There are a few options, but they all have drawbacks. First, you could reimplement the feature without hooks but I hope you don&rsquo;t do that. Alternatively, you could create a <a href="https://reactjs.org/docs/higher-order-components.html">HOC</a> to wrap your component and pass down the hooks functionality. This creates a lot of boilerplate and doesn&rsquo;t compose well though. The only sustainable option is to suck it up and convert that class component to a <code>FunctionComponent</code>. Depending on how much stuff is going on in that component, this can take a while and there&rsquo;s a non-trivial chance of introducing a bug.</p>
<p>This is a problem that lessens in time as more of your app is converted of course, but can be a real challenge to being productive when each time you touch a component you have to refactor it.</p>
<h2 id="3-ecosystem-challenges">3. Ecosystem Challenges</h2>
<p>The React docs claim hooks are <a href="https://reactjs.org/docs/hooks-intro.html#no-breaking-changes">&ldquo;Completely opt-in&rdquo;</a> and &ldquo;you don&rsquo;t have to learn or use Hooks right now if you don&rsquo;t want to&rdquo;. This seems a little disingenuous to me. Code doesn&rsquo;t change but the world around it does.</p>
<p>As a library author, you&rsquo;re forced to make some choices in the now fractured ecosystem. Do you add support for hooks? Do you also maintain support for class components? For how long? This is a hard question to answer, especially if the library leaned heavily into <a href="https://reactjs.org/docs/higher-order-components.html">HOC&rsquo;s</a>.</p>
<p>As a library consumer, what happens when you need to upgrade a library but it made the choice to only support hooks? As the ecosystem evolves and adopts hooks you&rsquo;ll be coerced more and more into using them. This is another argument for reducing your dependencies but that&rsquo;s a story for another day.</p>
<h2 id="4-the-rules-of-hooks-limit-your-design">4. The Rules of Hooks Limit Your Design</h2>
<p>I&rsquo;m assuming anyone who&rsquo;s read this far knows about the <a href="https://reactjs.org/docs/hooks-rules.html">Rules of Hooks</a>. The reason for the rules makes sense given that React uses <a href="https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e">call order to maintain state</a> but this constraint can really limit how you organize and optimize your code.</p>
<p>In a recent example, I was building a hook that returns a lookup table where many of the values in the table were hooks themselves.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">function</span> useLookupTable(param){
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span> {
</span></span><span style="display:flex;"><span>      a<span style="color:#000;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">default</span><span style="color:#000;font-weight:bold">:</span> useADefault(param)
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      b<span style="color:#000;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#000;font-weight:bold">default</span><span style="color:#000;font-weight:bold">:</span> useBDefault()
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      . . . many more entries
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>To avoid returning a big new object every time this hook is called I wanted to memoize the value. Lets try wrapping it in a <code>useMemo</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">function</span> useLookupTable(param){
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span> useMemo(() =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">return</span> {
</span></span><span style="display:flex;"><span>        a<span style="color:#000;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#000;font-weight:bold">default</span><span style="color:#000;font-weight:bold">:</span> useADefault(param)
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        b<span style="color:#000;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#000;font-weight:bold">default</span><span style="color:#000;font-weight:bold">:</span> useBDefault()
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        . . .
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>Oops. React Hook &ldquo;useADefault&rdquo; cannot be called inside a callback. So let&rsquo;s pull all the hooks out and include them as dependencies:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span>  <span style="color:#000;font-weight:bold">function</span> useLookupTable(param){
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> aDefault <span style="color:#000;font-weight:bold">=</span> useADefault(param);
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">const</span> bDefault <span style="color:#000;font-weight:bold">=</span> useBDefault();
</span></span><span style="display:flex;"><span>    . . .
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#000;font-weight:bold">return</span> useMemo(() =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#000;font-weight:bold">return</span> {
</span></span><span style="display:flex;"><span>        a<span style="color:#000;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#000;font-weight:bold">default</span><span style="color:#000;font-weight:bold">:</span> aDefault
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        b<span style="color:#000;font-weight:bold">:</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#000;font-weight:bold">default</span><span style="color:#000;font-weight:bold">:</span> bDefault
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        . . .
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }, [aDefault, bDefault, . . . ])
</span></span><span style="display:flex;"><span>  }
</span></span></code></pre></div><p>We&rsquo;ve now got rid of the error (and introduced an annoying number of temporary variables) but we&rsquo;re still returning a new table every time. Why? Oh, it&rsquo;s because one of the values is a new array which fails the dependency equality check every time. Enforcing each of these <code>useXDefault</code> hooks to return a stable value (like with a <a href="https://reactjs.org/docs/hooks-reference.html#useref">useRef</a>) seems like a bad call since it would overly complicate them, and even if I did, as the table grows (and if each entry has multiple hooks) the dependency array soon gets out of control.</p>
<p>In this case I think the best solution is to refactor all these hooks to <em>not</em> be hooks and just regular functions that take their dependencies explicitly so that you can call them inline like in the second example and the dependency array for useMemo is manageable.</p>
<p>These sorts of battles with how you have to organize your code in a world of hooks is common, and it can take a lot of iteration, thought, and time to come up with a design that feels good.</p>
<h2 id="5-they-complicate-control-flow">5. They Complicate Control Flow</h2>
<p>Using a few <code>useState</code> hooks is pretty easy to understand, but once you start needing to use all the other kinds of hooks up and down the component tree it becomes very difficult to follow the execution order of your code. How well can you reason about code if you don&rsquo;t understand the order it&rsquo;s executing in?</p>
<p>Here&rsquo;s a little quiz to see how well you can follow some toy examples.</p>
<iframe src="https://docs.google.com/forms/d/e/1FAIpQLSdAG7QtzNT_-rl6j1bqJpJaq-5ZaLR_hnWUGvtVCR4vD5O_sA/viewform?embedded=true" width="640" height="1065" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe>
<p>If you aced it, congrats! I&rsquo;m guessing you either ran them yourself or you had to sit back and scratch your head a bit. These are certainly contrived examples, but we all should be able to agree that simpler and less magical code is easier to work with than the alternative.</p>
<h2 id="in-conclusion">In Conclusion</h2>
<p>I&rsquo;m not asking you to never use hooks or remove them from your project. As I said in the intro, I would still use hooks in a new project, and I think they do improve the prior situation of <a href="https://reactjs.org/docs/higher-order-components.html">HOC&rsquo;s</a> creating component hierarchies deeper than the Mariana Trench. But I also think they&rsquo;re not the &ldquo;Computing Promised Land&rdquo;. It might be a while before we get there though.</p>
<p>So until then, just use them judiciously.</p>
<p>P.S. Library authors, please try to keep things simple!</p>
<p>Cross posted to <a href="https://medium.com/indigoag-eng/a-critique-of-react-hooks-6de10e8f14e1">https://medium.com/indigoag-eng/a-critique-of-react-hooks-6de10e8f14e1</a></p>
<p><strong><a href="/a-critique-of-react-hooks-addendum/">Quiz results and a few more thoughts in the addendum!</a></strong></p>
<p><br/><br/>
Lots of comments also posted over on Hacker News! <a href="https://news.ycombinator.com/item?id=22995928">https://news.ycombinator.com/item?id=22995928</a></p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>Unity Save Game Editor</title>
      <description>Hack your unity save games with this handy save game editor.</description>
      <link>https://dillonshook.com/unity-save-game-editor/</link>
      <pubDate>Mon, 18 Nov 2019 07:58:08 +0000</pubDate>
      
      <guid>https://dillonshook.com/unity-save-game-editor/</guid>
      
      <content:encoded><![CDATA[<div style="">
<div style="display: inline-block;width: 75%;vertical-align: top;padding-right: 3%;">
<p>In game development there's a lot of scratching your own itch and my latest diversion is a great example of that.
<p>More often than I care to count, when I&rsquo;m working on a new feature for <a href="https://macrocosm-game.com/">Macrocosm</a> something gets in a weird state and I need to see what&rsquo;s going on with the save.  Once I figure out what happened and attempt a fix, it would be super nice to be able to then edit the save game and put it back to a correct state to test from.</p>
</p>
</div>
<div style="display: inline-block;width: 20%;">
<a href="https://macrocosm-game.com/" target="_blank" aria-label="Macrocosm Game">
<img style="height: auto; width: 100%;" src="images\1.jpg" alt="Macrocosm Stage 1" />
</a>
</div>
</div>
<p>After thinking about it enough I decided to take the time from main game development and make this tool that should speed up future dev time.</p>
<p>Enter the Unity Save Game Editor.</p>
<p><img src="https://res.cloudinary.com/dillonshook/image/upload/v1574031372/save_editor_xctbss.gif" alt="In action"></p>
<p>Once you set your save game path &amp; extension and choose which save file you want to work on, the editor will display a tree of all the loaded data you can then dig into and edit!</p>
<p>On each row there are contextual controls for resetting values, and adding or removing items from a collection. While there are a few more things I wish it had, it suits my needs for now and I think it&rsquo;s in a good state to give it to the world and hopefully have other people help contribute to it to.</p>
<p>This will work if your save game is simply serializing a data structure, something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-C#" data-lang="C#"><span style="display:flex;"><span><span style="color:#000;font-weight:bold">public</span> <span style="color:#000;font-weight:bold">void</span> SaveGame(){
</span></span><span style="display:flex;"><span>    BinaryFormatter bf = <span style="color:#000;font-weight:bold">new</span> BinaryFormatter();
</span></span><span style="display:flex;"><span>    FileStream file = File.Create(savePath);
</span></span><span style="display:flex;"><span>    bf.Serialize(file, gameData);
</span></span><span style="display:flex;"><span>    file.Close();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And on that note, go check out the project on GitHub!</p>
<p><a href="https://github.com/dshook/UnitySaveEditor">https://github.com/dshook/UnitySaveEditor</a></p>
<p>Once you have it copied into your project in an editor folder (in my case: Assets/Editor/SaveEditor) you should see the menu item to open it in Unity&rsquo;s window menu:</p>
<p><img src="https://res.cloudinary.com/dillonshook/image/upload/v1589589987/SaveEditor_pvj7hk.jpg" alt="Unity SaveEditor Menu"></p>
]]></content:encoded>
    </item>
    
    
    <item>
      <title>Hex Map Area Borders</title>
      <description>Today we&amp;rsquo;re going to learn how to create a nice border around the perimeter of an area in a hex map using Unity and a LineRenderer. Here I have it as a dotted purple line but you can make it look like anything you want with the flexibility of the line renderer!
The foundation of the hex code here is adapted from the Hex Map Series by Catlike Coding which has been super helpful for getting started.</description>
      <link>https://dillonshook.com/hex-city-borders/</link>
      <pubDate>Sun, 16 Jun 2019 04:00:00 +0000</pubDate>
      
      <guid>https://dillonshook.com/hex-city-borders/</guid>
      
      <content:encoded><![CDATA[<p>Today we&rsquo;re going to learn how to create a nice border around the perimeter of an area in a hex map using Unity and a LineRenderer. Here I have it as a dotted purple line but you can make it look like anything you want with the flexibility of the line renderer!</p>
<img class="center" src="https://res.cloudinary.com/dillonshook/image/upload/v1560648763/city_border_clean_bpvauk.gif" alt="Hex City Border" />
<p>The foundation of the hex code here is adapted from the <a href="https://catlikecoding.com/unity/tutorials/hex-map/">Hex Map Series</a> by Catlike Coding which has been super helpful for getting started. So if there&rsquo;s any bits of code I missed here it&rsquo;s likely from there, but please let me know in the comments if you&rsquo;re getting stuck or confused anywhere.</p>
<p>First up we&rsquo;ll need a little background code for the data we&rsquo;ll be operating on for the rest of this article.</p>
<script src="https://gist.github.com/dshook/83454d3fce98f5d344797a876331f5fa.js"></script>
<h2 id="1-find-the-area">1. Find the Area</h2>
<p>The first step is finding the set of cells that we&rsquo;ll use to find the perimeter around.  For my implementation I started with a variation on a flood fill algorithm that is also close to a Voronoi diagram.  There&rsquo;s a great article <a href="https://www.redblobgames.com/pathfinding/distance-to-any/">here</a> about it as well as many more great articles about hex maps, pathfinding and much more.</p>
<p>Since my city areas are changing infrequently I update them at startup and whenever one of the cities grows in influence (which affects their max radius of influence).</p>
<script src="https://gist.github.com/dshook/f3572d9c394691cc8abfb6cc967961ed.js"></script>
<h2 id="2-find-the-perimeter-of-the-area">2. Find the perimeter of the area</h2>
<p>Now that we have a set of cells for an area we need to find the cells on the perimeter of the area so that we can then find the points along edges of the cells for the line to go.</p>
<script src="https://gist.github.com/dshook/56ebc66f424e2bf852e2c8d1c26ed12f.js"></script>
<p>This works by finding the top right-most cell and then marches around the perimeter by prioritizing the direction to travel in.  For example, if our last move was to the cell south east of us our priorities would look like this:</p>
<img src="images/HexDirections.png" />
<p>This priority ordering will ensure we stay along the outside edge of the area.</p>
<p>We also need to include the 5th priority as going back to where we came from in the case where there is a peninsula of cells that stick out from the rest.  In that case our perimeter will contain those cells twice as the algorithm marches out and back, but this is what we want so that the line can follow around the outside.</p>
<h2 id="3-find-the-points-for-the-line">3. Find the points for the line</h2>
<p>And now for the fun part: finding all the world coordinates for our line renderer.  We&rsquo;ll do this by iterating over the perimeter cells keeping track of which direction we came from and which direction we&rsquo;re going.  These directions will let us determine which kind of &ldquo;bend&rdquo; we&rsquo;re dealing with.  This bend type will then determine how many corners of the current hex cell we&rsquo;ll need to add to our line before moving on.</p>
<img src="images/HexBends.png" />
<script src="https://gist.github.com/dshook/ea62c52c03e4a540517214ed03766388.js"></script>
<p>And that&rsquo;s the gist of it! All that&rsquo;s left is the function to tie it all together:</p>
<script src="https://gist.github.com/dshook/2161ae7db9a48eee3a41e00fa7306789.js"></script>
<p>When you set up your LineRenderer just make sure the <code>Use World Space</code> and <code>Loop</code> boxes are checked.</p>
<p>Hope you learned something and saved some time!</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>