<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peter Strøiman</title>
    <description>The latest articles on DEV Community by Peter Strøiman (@stroiman).</description>
    <link>https://dev.to/stroiman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1510381%2F22f48b63-eaaf-41e1-8ac1-d265c7568b7f.jpeg</url>
      <title>DEV Community: Peter Strøiman</title>
      <link>https://dev.to/stroiman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stroiman"/>
    <language>en</language>
    <item>
      <title>Getting Started with Neovim</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Mon, 05 May 2025 11:33:36 +0000</pubDate>
      <link>https://dev.to/stroiman/getting-started-with-neovim-4ml1</link>
      <guid>https://dev.to/stroiman/getting-started-with-neovim-4ml1</guid>
      <description>&lt;p&gt;You are interested in learning vim? Good. What you learn will be useful knowledge forever.&lt;/p&gt;

&lt;p&gt;I will guarantee you that once you learn vim, it will affect how you use any editor. Even if you don't use vim; whichever editor you use, you will want to install plugins to emulate vim behaviour.&lt;/p&gt;

&lt;p&gt;Vim emulation plugins exist for virtually every text editor, including general writing applications such as Obsidian.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn modal editing
&lt;/h2&gt;

&lt;p&gt;Vim's unique approach to editing originates from operating in different &lt;em&gt;modes&lt;/em&gt;, separating the activity of &lt;em&gt;writing text&lt;/em&gt; from &lt;em&gt;editing text&lt;/em&gt;. Once you master this approach, there is no going back; as the editor ceases to get in your way. You don't reach for the mouse to navigate, as your fingers are already close to the keys you need to press.&lt;/p&gt;

&lt;p&gt;Vim has a dedicated program &lt;code&gt;vimtutor&lt;/code&gt; to teach the fundamentals. Run with the &lt;code&gt;-h&lt;/code&gt; argument to which options it has.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ vimtutor -h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just run &lt;code&gt;vimtutor&lt;/code&gt; without arguments to start from the beginning in English.&lt;/p&gt;

&lt;p&gt;Configuring vim/neovim into a usable environment can be a mouthful. You can install a "distro" that come provides a curated set of plugins. I will suggest you try one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.lazyvim.org/" rel="noopener noreferrer"&gt;LazyVim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nvim-lua/kickstart.nvim" rel="noopener noreferrer"&gt;kickstart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But now, you are started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimenting with configuration
&lt;/h2&gt;

&lt;p&gt;Feeling adventurous, try both, and compare for yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I do not know if or how the following tip can be translated to Windows.&lt;/p&gt;

&lt;p&gt;By default, neovim loads the configuration from &lt;code&gt;$HOME/.config/nvim&lt;/code&gt;, but the last part of the path can be configured with the environment variable &lt;code&gt;NVIM_APPNAME&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;So you could install the two configurations in two separate folders and switch between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;NVIM_APPNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lazy nvim &lt;span class="c"&gt;# loads from $HOME/.config/lazy&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;NVIM_APPNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kick nvim &lt;span class="c"&gt;# loads from $HOME/.config/kick&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've recreated my configuration from scratch a few times, and I always start in a new folder, using this approach; keeping my existing configuration that I know is working as a fallback; until I'm happy with the new setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn From the Old Masters.
&lt;/h2&gt;

&lt;p&gt;I also recommend watching &lt;a href="http://vimcasts.org/" rel="noopener noreferrer"&gt;vim-casts&lt;/a&gt; by Drew Neil. They are rather old, targeting older versions, so do take the information with a grain of salt. But the principles of vim; buffers, registers, navigation, tab, macros, etc.; all that is still valuable information today; and will help grok fundamentals of vim and neovim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use, Then Biuld
&lt;/h2&gt;

&lt;p&gt;When you've used a distro for a year; I suggest you "create" a configuration from scratch, possibly following a video tutorial. &lt;/p&gt;

&lt;p&gt;And after you've used that for a year, I suggest you create your own from scratch.&lt;/p&gt;

</description>
      <category>neovim</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Why You Should Boycott VS Code</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Mon, 05 May 2025 09:57:46 +0000</pubDate>
      <link>https://dev.to/stroiman/why-you-should-boycott-vs-code-4a7i</link>
      <guid>https://dev.to/stroiman/why-you-should-boycott-vs-code-4a7i</guid>
      <description>&lt;p&gt;Microsoft's Visual Studio Code, commonly just called VS Code, is a great piece of software. This is in no small part due to all extensions on the extensive marketplace providing extreme flexibility. It has proven it to be adaptable to most, if not all, software development projects.&lt;/p&gt;

&lt;p&gt;My editor of choice, neovim, provides no such benefits out of the box, requiring me to configure everything, install language plugins, code completion, myself. &lt;/p&gt;

&lt;p&gt;Vim and neovim are by far more powerful editors, but lacking the ease-of-use of VSCode, and easy discoverability of new features; I don't recommend beginners to use these. Configuration is done through code, not a nice UI allowing you to browse a marketplace and take popularity into consideration when choosing between different plugins for the same task.&lt;/p&gt;

&lt;p&gt;And while both pieces of software are distributed as open-source software, Microsoft is actively preventing the modification and distribution of the source code under the ethos of open-source.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Open-source Software?
&lt;/h2&gt;

&lt;p&gt;For software to be considered "open-source", you must be able to make modifications, distribute the modifications, and run the software or your modified software for any purpose without any restrictions.&lt;/p&gt;

&lt;p&gt;The term "open-source" is predated by "free software"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The FSF (free software foundation)'s goal was to promote the development and use of free software, which they defined as software that grants users the freedom to run, study, share, and modify the code.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Free software" had a unclear meaning however, as "free" is unclear. It was free as in "free speech", not "free beer". "Open-source" was coined instead to avoid confusion. &lt;/p&gt;

&lt;p&gt;While Richard Stallman and FSF didn't adopt this, as the term might indicate you only had the ability to &lt;em&gt;inspect&lt;/em&gt; software, the &lt;a href="https://opensource.org/" rel="noopener noreferrer"&gt;Open Source Initiative&lt;/a&gt; still governs the core principles; that open-source software can be modified and shared, regardless of its purpose.&lt;/p&gt;

&lt;p&gt;OSI have reviewed many licenses. They have have published a list of approved licenses that meet the criteria of open-source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributing Source Code is not Open-source.
&lt;/h3&gt;

&lt;p&gt;MongoDB is a database engine by MongoDB Inc.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; who provides hosted MongoDB databases as a service. The source code for MongoDB is available under the conditions of their own proprietary SSPL license. The license provides a lot of flexibility in how you can use the software; you can even use it with no licensing fees to power your own web applications. But if you offer MongoDB as a service; the license impose some extreme requirements.&lt;/p&gt;

&lt;p&gt;MongoDB's approach is IMHO reasonable and fair. They are basically making the software freely available to anyone who is not a direct competitor to their own hosted service.&lt;/p&gt;

&lt;p&gt;But it's not open-source, as you do not have the freedom to modify and run the code without restrictions. The license was rejected as an open-source license by OSI, Red Hat, and Debian.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Visual Studio Code Violates the Ethos
&lt;/h2&gt;

&lt;p&gt;Visual Studio Code is distributed under the MIT license, a permissive license that allows you to do &lt;em&gt;anything&lt;/em&gt; with the source code. This license is recognised by the OSI as an open-source license. And quite predictably, a few forks of VSCode exists. &lt;a href="https://vscodium.com/" rel="noopener noreferrer"&gt;VSCodium&lt;/a&gt; seems to be the most popular.&lt;/p&gt;

&lt;p&gt;This was all perfectly well, if it wasn't because of the barriers Microsoft impose on derived works ...&lt;/p&gt;

&lt;p&gt;VSCode has little value without any extensions. The VSCode marketplace has an almost infinite list of extensions, so almost no matter which tool or language you use, you will find an extension to provide support. If you want a new programming language to succeed, it's almost vital that you have proper VSCode integration.&lt;/p&gt;

&lt;p&gt;Microsoft also provides VSCode extensions as well. And this is where the problem lies, as you are not allowed to use these extensions in any a fork of VSCode. Two popular extensions provided by Microsoft are  &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance" rel="noopener noreferrer"&gt;pylance&lt;/a&gt; and &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools" rel="noopener noreferrer"&gt;C/C++ for Visual Studio Code&lt;/a&gt;. Both of these contain the following condition in their license:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You may install and use any number of copies of the software only with Microsoft Visual Studio, Visual Studio for Mac, Visual Studio Code, Azure DevOps, Team Foundation Server, and successor Microsoft products and services to develop and test your applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The "Open-source Theatre"
&lt;/h3&gt;

&lt;p&gt;I call this the "Open-source Theatre", somewhat inspired by the "Security Theater"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security theater&lt;/strong&gt; is the practice of implementing &lt;a href="https://en.wikipedia.org/wiki/Security" rel="noopener noreferrer"&gt;security measures&lt;/a&gt; that are considered to provide the feeling of improved security while doing little or nothing to achieve it.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So while Microsoft distributes VSCode itself as an open-source product, providing a feeling of commitment to open-source, they are not just "doing little" to achieve it, they are actively fighting against this. The ethos dictates that you should be able to make modifications and run without restrictions, but they are actively placing barriers on those modifications.&lt;/p&gt;

&lt;p&gt;I would call this a legal loophole, as they managed to place open-source software in a context where they can violate the principles of open-source in the surrounding context.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Value of VSCode is it's Community.
&lt;/h3&gt;

&lt;p&gt;As I mentioned, I think VSCode is a good product, particularly as your first editor.&lt;/p&gt;

&lt;p&gt;The value of VSCode comes from the ecosystem of extensions. While &lt;em&gt;some&lt;/em&gt; are developed by Microsoft, the majority are developed by individuals or small teams made who provided their solutions as free and open-source software. In effect, a large number of individual developers are increasing the value of VSCode, often in their spare time; but under the assumption that they are contributing to an open-source community.&lt;/p&gt;

&lt;p&gt;This isn't just a theoretical case, of some outdated licenses they forgot to update. Microsoft are actively enforcing the restriction, as &lt;a href="https://github.com/VSCodium/vscodium/issues/2300" rel="noopener noreferrer"&gt;this issue from the vscodium project&lt;/a&gt; shows. The plugin used to work from VSCodium, but Microsoft has &lt;strong&gt;actively taken measures to prevent this&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  To be fair
&lt;/h3&gt;

&lt;p&gt;While researching for this article, I notice that the restrictions is in the license for the &lt;em&gt;compiled .vsix&lt;/em&gt; file distributed to the VSCode marketplace. Their &lt;em&gt;source code&lt;/em&gt; have more permissive licenses. Maybe you could compile the extensions your self and use them? But the situation isn't clear from what I learned.&lt;/p&gt;

&lt;p&gt;pylance has a license I think should be fine, but it's not an OSI approved license.&lt;sup id="fnref4"&gt;4&lt;/sup&gt; At first, things looked better for the C/C++ extension, as it uses the OSI approved MIT license. But it also includes a notice that the code depends on runtime files "built and distributed by Microsoft; these are governed by the more restrictive proprietary license".&lt;/p&gt;

&lt;p&gt;I did not research whether you could legally use these plugins if you were to compile them yourself. So these two plugin &lt;em&gt;could be possible&lt;/em&gt;, but requiring extra work.&lt;/p&gt;

&lt;p&gt;It doesn't change the fact that Microsoft are taking steps to actively prevent using the compiled version distributed to the VSCode marketplace, thus actively imposing barriers for VSCode forks.&lt;/p&gt;

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

&lt;p&gt;The revolution of open-source software is by far the most important change in the software industry I've witnessed during my career. When I started, software development required the purchase of tools, compilers and component libraries. Bugs in libraries meant bugs in your own code; with no ability to fix them yourself.&lt;/p&gt;

&lt;p&gt;Open source has democratised software development. Large companies like Netflix, Meta, Google, have distributed tools as free and open-source software.&lt;/p&gt;

&lt;p&gt;And the linux kernel is a massive open-source project. It's safe to say that the internet runs on Linux; and Google is also making a fair bit of profit from 2.5 billion active Android devices.&lt;/p&gt;

&lt;p&gt;Even VSCode is build on the open-source Electron framework, created by Github (later acquired by Microsoft) as a foundation for their Atom editor. And Electron is based on Chromium, the open-source core powering Google Chrome.&lt;/p&gt;

&lt;p&gt;I don't argue that companies are obligated to distributing their products as open-source. Not at all. One of my favourite pieces of software, &lt;a href="https://obsidian.md/" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt; is closed source, and I have no objection to that. They as a company need to make a profit, and they are free to chose their own strategy.&lt;sup id="fnref5"&gt;5&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;But I do expect when a company release a product as open-source, they support the ability to modify and distribute, rather than actively imposing barriers for derived works, while using this legal loophole to justify an "open-source" label.&lt;/p&gt;

&lt;p&gt;Open-source is the foundation for a free internet.&lt;sup id="fnref6"&gt;6&lt;/sup&gt; Open-source is important. Open-source is worth fighting for.&lt;/p&gt;

&lt;p&gt;But in the land of open-source software, VSCode is a wolf in sheep's clothing. It should be exposed for what it is.&lt;/p&gt;

&lt;p&gt;If you are using VSCode, I encourage you to try something new. See if VSCodium works for you. Or if you are ready to move on to vim or neovim (see my &lt;a href="https://dev.to/stroiman/getting-started-with-neovim-4ml1"&gt;getting started guide&lt;/a&gt; for some batteries-included options). Or try &lt;a href="https://xkcd.com/378/" rel="noopener noreferrer"&gt;emacs&lt;/a&gt;.&lt;sup id="fnref7"&gt;7&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Hell, even check out JetBrains' products and see if they work for you. JetBrains have build development tools for decades, and have an insane amount of experience in that field. Unfortunately, their tools are closed-source&lt;sup id="fnref8"&gt;8&lt;/sup&gt; paid products.&lt;sup id="fnref9"&gt;9&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;But JetBrains don't pretend to be what they are not.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Open_source#Open_source_as_a_term" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Open_source#Open_source_as_a_term&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MongoDB" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/MongoDB&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Security_theater" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Security_theater&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Pylance source code is distributed under the Creative Commons Attribute 4.0 International license. Creative Commons is normally used for other creative arts, such as images, graphics, audio, etc. I've never seen it applied to software before. AFAIK it does follow the ethos of open-source software. According to my research is not an OSI approved license. But that could simply be because it was never evaluated for that purpose. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Obsidian is a safe investment because data is stored in an open format, markdown files. If at any point in the future I should want to abandon Obsidian, while I ca't rely on a fork suited for my purposes, I can still access my notes; using a plain text editor; like vim. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;"Free" as in "free speech". An internet free as in "free beer" is very unlikely to support free speech. But if it does, it would be due to open-source projects. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;Emacs seem to have decreased significantly in popularity. Many of the advantages emacs previously had over vim are easily achievable in neovim today. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;JetBrains have a staggering amount of open-source projects, including the Kotlin programming language. But to the best of my knowledge, their IDEs are closed source. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;There appears to be free plans for non-commercial use.  ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>vscode</category>
      <category>opensource</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Using Go maps as a simple Fake repository</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Mon, 21 Apr 2025 09:28:51 +0000</pubDate>
      <link>https://dev.to/stroiman/using-go-maps-as-a-simple-fake-repository-4i9b</link>
      <guid>https://dev.to/stroiman/using-go-maps-as-a-simple-fake-repository-4i9b</guid>
      <description>&lt;p&gt;This article explores how you can take advantage of Go's interface mechanics to create very simple stubs for testing.&lt;/p&gt;

&lt;p&gt;The example here is a piece of code that sends a "password confirmation email" to a user. The implementation needs to look up an account, to generate the correct email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending an account validation email
&lt;/h2&gt;

&lt;p&gt;The actual process doesn't run in the same transaction as the original user request; it runs as a reaction to a domain event, that a user has registered with the site. The event itself doesn't contain any user information, only an &lt;code&gt;AccountID&lt;/code&gt;; as well as a generic &lt;code&gt;EventID&lt;/code&gt;. To generate and send the email, the implementation needs to get the account with that ID.&lt;/p&gt;

&lt;p&gt;You could imagine a simple type depending on two interface types, one for a repository to load the account, and one for sending an email:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;EmailValidator&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AccountRepository&lt;/span&gt;
    &lt;span class="n"&gt;Mailer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;EmailValidator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ProcessEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DomainEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AccountRepository&lt;/code&gt; might accommodate many different purposes, and have a range of methods for those purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AccountRepository&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;AccountID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;FindByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But for the purpose of the &lt;code&gt;EmailValidator&lt;/code&gt;, this interface is bloated. The &lt;code&gt;EmailValidator&lt;/code&gt; only needs the first method, &lt;code&gt;GetAccount&lt;/code&gt;. This design violates the &lt;a href="https://en.wikipedia.org/wiki/Interface_segregation_principle" rel="noopener noreferrer"&gt;Interface Segregation Principle&lt;/a&gt;; the &lt;code&gt;EmailValidator&lt;/code&gt; is forced to depend on methods it doesn't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrowing down the interface
&lt;/h2&gt;

&lt;p&gt;The code base will have an implementation of an &lt;code&gt;AccountRepository&lt;/code&gt;  somewhere, providing the capabilities of finding, and updating &lt;code&gt;Account&lt;/code&gt; information, bringing cohesion to serialising/deserialising account entities. But the interface we depend on need not specify all the capabilities of the implementation. Multiple interfaces can exist, and the same implementation can satisfy them all.&lt;/p&gt;

&lt;p&gt;When I first started writing Go, it was after 15 years of C# experience. For the components that are wired up through an IoC container, it was a common approach to have an 1-to-1 relationship between classes and interfaces, making the interface effective &lt;em&gt;describe the capabilities&lt;/em&gt; of a component in the system.&lt;/p&gt;

&lt;p&gt;But we can invert this principle, and let the interface &lt;em&gt;describe the dependencies&lt;/em&gt; of a component. So instead I create the &lt;code&gt;AccountLoader&lt;/code&gt; interface; describing what the &lt;code&gt;EmailValidator&lt;/code&gt; depends on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;registration&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AccountLoader&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;EmailValidator&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AccountLoader&lt;/span&gt;
    &lt;span class="n"&gt;Mailer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By having the interface next to the &lt;code&gt;EmailValidator&lt;/code&gt; this further enhances the relationship that this interface describes the dependencies of the validator component.&lt;/p&gt;

&lt;p&gt;Go's type system makes this approach viable, as types do not need to know about the interfaces they implement. C#, Java, or C++ require that class definitions explicitly state which interfaces they support. While the same granularity is possible, language designs of these languages impose constraints to code structure, and it's not an approach I've witnessed during my 15 years of experience with C#.&lt;/p&gt;

&lt;p&gt;In reality, Go is a compiled language that supports Duck Typing on interface values.&lt;/p&gt;

&lt;p&gt;So this &lt;em&gt;change&lt;/em&gt; didn't break any implementation. Any previous account repository implementation is still a valid slot to fill in as a dependency to the validator type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a fake repository for testing
&lt;/h2&gt;

&lt;p&gt;A common practice for testing in languages like C# is to use a mocking library to automatically generate programmable mocks to inject as dependencies.&lt;/p&gt;

&lt;p&gt;With single-method interfaces, a hand-written stub or fake becomes a much more viable solution. For the &lt;code&gt;AccountLoader&lt;/code&gt;, a simple &lt;code&gt;map&lt;/code&gt; type is enough when it just has to find an entity by ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;fakeRepo&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AccountID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;fakeRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;AccountID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ErrNotFound&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation demonstrates another property, that any type can have methods, meaning any type can be a valid implementation of an interface.&lt;/p&gt;

&lt;p&gt;Creating a helper to convert a &lt;code&gt;found bool&lt;/code&gt; value to an &lt;code&gt;error&lt;/code&gt; can reduce the code further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;fakeRepo&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AccountID&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;fakeRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;AccountID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;foundToError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;foundToError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrNotFound&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fake repository is now just 5 lines of code. While the abstraction through the &lt;code&gt;foundToError&lt;/code&gt; helper may at first make the code less readable; if this is a helper function used ubiquitously in test code, it becomes part of the mental model, making the reduced version just as readable as the first implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the fake repository
&lt;/h2&gt;

&lt;p&gt;Writing a new test using the fake repository is quite easy. Using &lt;code&gt;map&lt;/code&gt; as the underlying type allows it to be constructed using a &lt;a href="https://go.dev/doc/effective_go#maps" rel="noopener noreferrer"&gt;map-literal&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestSendEmailValidationChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// InitAccount is assumed create a valid initialized account, i.e. a non-zero ID&lt;/span&gt;
    &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;InitAccount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateEmailValidationRequestEvent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;mailer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;FakeMailer&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;EmailValidator&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Mailer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;mailer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;mailer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssertOutgoingEmailTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an initialising the fake repository is just a one-liner embedded in the &lt;code&gt;EmailValidator&lt;/code&gt; struct literal.&lt;/p&gt;

&lt;p&gt;This makes the test code simpler than any implementation using a programmable mock which would that you first create the mock, then program expectations, before it can be used in a dependency.&lt;/p&gt;

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

&lt;p&gt;Go's interfaces provides great flexibility because of two features, separating it from most compiled languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Types do not need to know about the interfaces they implement&lt;/li&gt;
&lt;li&gt;Any type can have methods and implement an interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it a viable approach to create small interfaces that &lt;em&gt;describe the dependencies&lt;/em&gt; of a component, rather than the &lt;em&gt;capabilities&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;You can easily create simple implementations for testing, often making the tests simpler than using a programmable mocking framework (they exist for Go as well, but not as used).&lt;/p&gt;

&lt;p&gt;Furthermore, as the hand written implementations implement an interface describing a dependency of the component under test; so they are coupled to the components they are used to tests, strengthening cohesion in test code.&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>The Beauty of Go, Introduction</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Mon, 21 Apr 2025 09:28:17 +0000</pubDate>
      <link>https://dev.to/stroiman/the-beauty-of-go-introduction-5bo3</link>
      <guid>https://dev.to/stroiman/the-beauty-of-go-introduction-5bo3</guid>
      <description>&lt;p&gt;This series is about &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, a simple, yet powerful, language that has some unique features in its design.&lt;/p&gt;

&lt;p&gt;Go has some unique features, and is together with OCaml the only compiled language I've used that supports Duck Typing, i.e., the ability for a type to conform to an "interface" without the type definition explicitly stating this.&lt;/p&gt;

&lt;p&gt;In this series, I share some of the interesting aspects of Go, and share some solutions that take advantage of the capabilities of the Go language.&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>How TDD reduces cost by reducing waste</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Wed, 16 Apr 2025 12:23:59 +0000</pubDate>
      <link>https://dev.to/stroiman/how-tdd-reduces-cost-by-reducing-waste-48j4</link>
      <guid>https://dev.to/stroiman/how-tdd-reduces-cost-by-reducing-waste-48j4</guid>
      <description>&lt;p&gt;For more than half my career, I've been a practitioner of TDD, Test-driven development. The practice of writing &lt;em&gt;test code&lt;/em&gt; before writing &lt;em&gt;production code&lt;/em&gt;. I strongly believe that it is by far the most important skill any developer can learn.&lt;/p&gt;

&lt;p&gt;Unfortunately, TDD is often poorly understood, even by many who practice it; yet, poorly practiced TDD will still often be beneficial. But a challenge developers can face is that management may perceive TDD as increased cost, when the exact opposite is true; TDD &lt;em&gt;reduces cost&lt;/em&gt; because it reduces waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Waste
&lt;/h2&gt;

&lt;p&gt;Agile software development is sometimes compared to Lean Manufacturing, which is highly influenced by the &lt;a href="https://en.wikipedia.org/wiki/Toyota_Production_System" rel="noopener noreferrer"&gt;Toyota Production System&lt;/a&gt;. In the 1988 booklet published by Toyota they describe it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The TPS is a framework for conserving resources by eliminating waste.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples of waste in a production facility could be excessive inventory, overproduction, moving or transporting parts or equipment, underutilised workers, idle time, relearning, and obviously, defective products.&lt;/p&gt;

&lt;p&gt;While civil engineering, and industrial production, are both poor analogies of software development, the concept of "waste" is comparable, although the sources of waste are different. &lt;/p&gt;

&lt;p&gt;Some examples of waste in software development are are: long running abandoned branches (unproductive paths), merge conflict resolution, knowledge loss through handoffs, unnecessary complexity, and obviously, product defects (bugs).&lt;/p&gt;

&lt;p&gt;Waste means added costs, and reduction of waste translates directly to reduced cost. &lt;/p&gt;

&lt;h2&gt;
  
  
  How TDD reduces waste
&lt;/h2&gt;

&lt;p&gt;The type of waste that is commonly understood to be reduced by a good test strategy is is &lt;em&gt;product defects/bugs&lt;/em&gt;. Thorough testing may &lt;em&gt;detect&lt;/em&gt; defects, but TDD can &lt;em&gt;prevent&lt;/em&gt; defects; i.e., many defects would never be committed to source control in the first place. A good TDD process would be able to cover &lt;em&gt;the majority of the required behaviour&lt;/em&gt;. TDD itself is not a complete solution to this problem, and there are tests worth writing &lt;em&gt;after code was written&lt;/em&gt;.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;But &lt;em&gt;defects is not the only source of waste reduced by TDD&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Truncating an unproductive path
&lt;/h3&gt;

&lt;p&gt;As developers, we are continuously making assumptions, often not realising it. We &lt;em&gt;assume&lt;/em&gt; that we can use component or technique &lt;em&gt;X&lt;/em&gt; to implement functionality &lt;em&gt;Y&lt;/em&gt;. We &lt;em&gt;assume&lt;/em&gt; that the path we take will solve the problem. We may write many lines of code &lt;em&gt;assuming it has the intended effect&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Occasionally, the assumption is flawed, and we must adjust the current work-in-progress accordingly. Occasionally, the assumption is flat-out invalid. Pursuing &lt;em&gt;X&lt;/em&gt; was so flawed to begin with that all work based on the assumption is useless for the purpose of &lt;em&gt;Y&lt;/em&gt;. The more work we have carried out, the more waste produced by following an &lt;em&gt;unproductive path&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The way to reduce unproductive paths is to validate the assumption as early as possible. If the assumption is technical in nature, executing snippets of code continuously is often the most efficient way to validate them.&lt;/p&gt;

&lt;p&gt;When the developer uses TDD they will quickly work towards a simple working solution using &lt;em&gt;X&lt;/em&gt;, initially with a lot of aspects uncovered, and even an incomplete tests. TDD effectively validates the design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduce time debugging
&lt;/h3&gt;

&lt;p&gt;At the time of writing this, I have not launched a debugger for 4 years! A debugger is a tool you may use when your code when the code does not have the &lt;em&gt;intended effect&lt;/em&gt;, and you can't immediately identify &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When working in small increments, running tests early, maybe even after one line of change at a time, you get the immediate feedback if a change had the intended effect.&lt;/p&gt;

&lt;p&gt;In poorly understood part about TDD is that &lt;em&gt;TDD is a feedback tool&lt;/em&gt;. When practicing TDD, the first iteration of a "test" may be nothing like a test at all; the test itself will change while developing a feature and will eventually mature into a "test" describing describe desired behaviour. Just as the production code evolves slowly, so may can test code.&lt;/p&gt;

&lt;p&gt;The granularity of the process can vary greatly. When taking the first step into a new area of the system, a very granular approach is likely. The more confident you are with the way forward, the greater the step you might take.&lt;/p&gt;

&lt;p&gt;But the role of the test is to describes the next step you intend take &lt;em&gt;towards having the desired behaviour&lt;/em&gt;, and have automated feedback that your work had the intended effect. And as issues are revealed early, debugging is rarely necessary, as the issue is easy to identify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decrease unnecessary complexity
&lt;/h3&gt;

&lt;p&gt;TDD can help avoid unnecessary complexity. How effectively depends on how well the test suite facilitate refactoring. Developers or teams new to TDD have a tendency to write test suites more closely coupled to implementation details, making the test suite resist refactoring. As developers gain more TDD experience, their test suites tend to describe the system at a higher level of abstraction, reflecting system behaviour rather than design decisions.&lt;/p&gt;

&lt;p&gt;The test suite now serves as a safety harness facilitating refactoring. Developers who fully embrace this will happily commit a simple naïve solution to the problem, defering complex design decisions and refactoring to patterns until new behaviour dictate.&lt;/p&gt;

&lt;p&gt;Often, the elegant solution doesn't present itself immediately, and by being able to defer the design decision; we have bought ourselves time, increasing the likelyhood of finding a simple solution to before the features demand it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Behaviour does not necessarily mean business rules. Behaviour can be technical in nature. Access token renewal is a behaviour of the system. It's not a behaviour domain experts may care about, but they may care about how fault-tolerant the system is overall; making developers add new behaviour such as retrying failed attempts using an exponential backoff mechanism. Writing tests for this in a way that can describe interaction with an external system as well as the passing or time is almost essential.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; Manual testing access token expiration against a real identity provide is almost guaranteed to be flaky if you need to wait 2 hours for access tokens to expire.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

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

&lt;p&gt;TDD is often misunderstood. The word "test" is somewhat misleading, as TDD is much more than testing.&lt;/p&gt;

&lt;p&gt;TDD is a process that incorporates instant feedback into the daily work of the developer. This helps reduce &lt;em&gt;unproductive paths&lt;/em&gt; as a source of waste while coding.&lt;/p&gt;

&lt;p&gt;An outcome of an effective TDD process is a test suite that helps detect regression of behaviour, reducing &lt;em&gt;defects&lt;/em&gt; as a source of waste; while facilitating refactoring.&lt;/p&gt;

&lt;p&gt;This permits developers to choose the simplest possible solution initially, having confidence they can add complexity when changing requirements dictate it, reducing &lt;em&gt;excessive complexity&lt;/em&gt; as a source of waste.&lt;/p&gt;

&lt;p&gt;Effective TDD is a skill that needs to be honed like any other skill. Once mastered, teams practicing TDD are able to reduce cost due to the reduction of waste.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: Examples
&lt;/h2&gt;

&lt;p&gt;This are two real examples from code bases I've worked on where a very granular process helped build the system, and solve one problem at a time. And in both cases, fast feedback was essential to moving forward quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovering the wrong assumption with GORM.
&lt;/h3&gt;

&lt;p&gt;I was new to GORM, a micro-ORM for Go. I started by creating a very simple database schema by hand, a single table with a &lt;code&gt;UUID&lt;/code&gt; primary key column. I created an entity type in code where the ID was initially represented by a &lt;code&gt;string&lt;/code&gt;, partly because Go doesn't natively have a UUID datatype.&lt;/p&gt;

&lt;p&gt;To see if I could insert and read, I created a &lt;em&gt;roundtrip test&lt;/em&gt;, a test that inserts the entity, then reads it again and verifies that the new copy is identical to the original. I wrote the following function to read an &lt;code&gt;Entity&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the function didn't work! The function returned an error, not an entity. After reading the documentation, I learned that when using a &lt;code&gt;string&lt;/code&gt; identifier, a different syntax is necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"id=?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this worked.&lt;/p&gt;

&lt;p&gt;Because I created a test as a means of fast feedback, and started with just basic static functions and a hardcoded connection, I was able to identify my incorrect assumption &lt;em&gt;immediately&lt;/em&gt;; significantly reducing the debugging process, compared to detecting the issue in the context of a more complex business rule implementation; and having to figure out where the problem lies.&lt;/p&gt;

&lt;p&gt;Over time I would rearrange code, grouping functionality into a dedicated repository with configurable connection options. But code structure was a different problem, and as I was venturing into unknown territory, I used a very granular approach and a tight feedback cycle. &lt;/p&gt;

&lt;p&gt;I first solved the problem of writing the actual lines of code that can read and write data; before addressing the problem of how to organise code in a larger codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  First time working with RabbitMQ
&lt;/h2&gt;

&lt;p&gt;The first time I had to work with RabbitMQ I had a similar problem. There were plenty of unknowns. How do I even connect? What is the valid connection string? How do I segregate data?&lt;/p&gt;

&lt;p&gt;The first couple of iterations involved visual inspection in the admin UI. For example, I tried to send a message to a queue. Messages should not be sent directly to queues, but exchanges, but that was a different problems to solve. The first queue was created manually in the rabbit admin UI. I wrote the code to send a message to the queue, and saved, letting the test framework run the code automatically. Then I inspected manually in the admin UI to see that the message would appear.&lt;/p&gt;

&lt;p&gt;After dealing with message publishing issues, I could then start to write code that would consume messages from a queue. The test would now send a message, and wait for one to appear. Next, I could add a verification step that the received message had an identical message body. I now had something that started to look like a test.&lt;/p&gt;

&lt;p&gt;Now I would manually delete the queue I had created, and the test would of course fail. I wrote the code to create missing queues, and the test would verify that the queue was created. Running the test again would verify that creating a missing queue would fail when it wasn't missing.&lt;/p&gt;

&lt;p&gt;At that point in time, I no longer needed the admin UI. It served as the best way to get feedback in the very early iterations, when I didn't have the code to read messages, and write both the publishing and consumption code in one sitting before working on the feedback would have required significantly more debugging time.&lt;/p&gt;

&lt;p&gt;Up to this point in time, all code existed only in a test file. I had dealt with the problem of how to write the lines of code that can actually interact with the RabbitMQ server. But the test provided feedback measured in milliseconds, and now I could rearrange the code into a &lt;code&gt;Publisher&lt;/code&gt; and a &lt;code&gt;Subscriber&lt;/code&gt; component in the system. The tests would at the same time be reorganised to describe publishing messages through the &lt;code&gt;Publisher&lt;/code&gt; and consuming them through the &lt;code&gt;Consumer&lt;/code&gt;, and every time I saved, a message would pass through RabbitMQ, confirming that the refactored code worked.&lt;/p&gt;

&lt;p&gt;Finally, I could address the problem at a higher level of abstraction. How domain events in one area of the system will eventually trigger another process to happen in another part of the system. For the most part, domain logic would be tested using a mocked &lt;code&gt;Publisher&lt;/code&gt;, verifying domain logic alone.&lt;/p&gt;

&lt;p&gt;Messages were still passed through RabbitMQ as part of the test suite. The actual message routing was controlled by RabbitMQ configuration, so if this wasn't tested, an essential part of the system behaviour would go untested.&lt;/p&gt;

&lt;p&gt;Domain event publishing and handling worked reliably, and I never had to debug the code.&lt;/p&gt;

&lt;p&gt;To this day, whenever I add RabbitMQ behaviour to a project, I follow the same path.&lt;/p&gt;







&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I will typically write TDD tests covering application and business logic as a whole, HTTP endpoints as a whole (described using HTTP headers, body, status codes, and mocking application logic), and data storage as a whole (using a real database). This leaves at least one type of test to add &lt;em&gt;after&lt;/em&gt; code was written. Do all layers connect properly? E.g., do IOC containers wire up dependencies correctly? Can a user complete a basic flow from login to checkout in a complete system? ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Libraries like lolex for JavaScript, and the experimental synctest package in Go 1.24 allow tests to simulate the passing of time; verifying behaviour without actually having to wait. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;This is the standard access token expiration for one major identity provider. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>tdd</category>
    </item>
    <item>
      <title>How Gost-DOM avoids making HTTP calls</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Tue, 18 Feb 2025 12:53:10 +0000</pubDate>
      <link>https://dev.to/stroiman/how-gost-dom-avoids-making-http-calls-4dka</link>
      <guid>https://dev.to/stroiman/how-gost-dom-avoids-making-http-calls-4dka</guid>
      <description>&lt;p&gt;This article is about the implementation of &lt;a href="https://github.com/gost-dom/browser" rel="noopener noreferrer"&gt;Gost-DOM&lt;/a&gt;, the headless browser written in &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Web applications written in Go are very easy to test. An web application serves HTTP requests from a single function, &lt;code&gt;ServeHTTP&lt;/code&gt;. Test code can just call this function to test the web application behaviour, but still express the test in terms of http requests, responses, headers, body, and status codes; rather than &lt;em&gt;controller method calls&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I wanted Gost-DOM to take advantage of this for two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Performance - The test is just calling Go code, avoiding the overhead of a TCP stack&lt;/li&gt;
&lt;li&gt;Isolation - By eliminating the need to manage TCP ports, it becomes much easier to run tests in isolation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Part 2 is really the most important part here. Although there is nothing preventing tests from running in isolation; the management of TCP ports increases complexity of the test setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTTP client
&lt;/h2&gt;

&lt;p&gt;I wanted to build this in a way that makes the core of the browser unaware of how HTTP requests are handled. Fortunately, Go's excellent standard library has the right tools out of the box.&lt;/p&gt;

&lt;p&gt;Outgoing HTTP requests are handled by an &lt;a href="https://pkg.go.dev/net/http#Client" rel="noopener noreferrer"&gt;&lt;code&gt;http.Client&lt;/code&gt;&lt;/a&gt; instance; which abstracts the transport layer through the &lt;a href=""&gt;&lt;code&gt;RoundTripper&lt;/code&gt;&lt;/a&gt; interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Transport specifies the mechanism by which individual&lt;/span&gt;
    &lt;span class="c"&gt;// HTTP requests are made.&lt;/span&gt;
    &lt;span class="c"&gt;// If nil, DefaultTransport is used.&lt;/span&gt;
    &lt;span class="n"&gt;Transport&lt;/span&gt; &lt;span class="n"&gt;RoundTripper&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;A great feature of Go is that many essential operations are abstracted by single-method interfaces. The &lt;code&gt;RoundTripper&lt;/code&gt; has just one method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;RoundTripper&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means, I just need to create a type implementing the &lt;code&gt;RoundTripper&lt;/code&gt; interface, that calls &lt;code&gt;ServeHTTP&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the interface
&lt;/h2&gt;

&lt;p&gt;The http handler function receives an &lt;code&gt;http.ResponseWriter&lt;/code&gt;. This is not a concrete type, but an interface. &lt;/p&gt;

&lt;p&gt;In addition to promoting a testable design of your production code, Go also supports great test tools in the &lt;a href="https://pkg.go.dev/net/http/httptest" rel="noopener noreferrer"&gt;&lt;code&gt;net/http/httptest&lt;/code&gt; package&lt;/a&gt;. The &lt;a href="https://pkg.go.dev/net/http/httptest#ResponseRecorder" rel="noopener noreferrer"&gt;&lt;code&gt;ResponseRecorder&lt;/code&gt;&lt;/a&gt; is a valid &lt;code&gt;ResponseWriter&lt;/code&gt; that test code can pass to the implementation, and simplifies dealing with response body streams.&lt;/p&gt;

&lt;p&gt;But the &lt;code&gt;ResponseRecorder&lt;/code&gt; is also so kind to actually generate a proper &lt;code&gt;*http.Response&lt;/code&gt;, that can be retrieved by calling, &lt;code&gt;ResponseRecorder.Result()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The type representing the request is the same in the &lt;code&gt;RoundTripper&lt;/code&gt; and the &lt;code&gt;Handler&lt;/code&gt;, so I made a copy, to avoid mutation in the tested server code affects the request object generated in the browser. The first version of the &lt;code&gt;TestRoundtripper&lt;/code&gt; looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// A TestRoundTripper is an implementation of the [http.RoundTripper] interface&lt;/span&gt;
&lt;span class="c"&gt;// that communicates directly with an [http.Handler] instance.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TestRoundTripper&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="n"&gt;TestRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRecorder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// Make a copy, so the http handler doesn't mutate the outgoing request&lt;/span&gt;
    &lt;span class="n"&gt;reqCopy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;reqCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; 
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqCopy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked fine in the beginning, but had some issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  A slightly odd design decision
&lt;/h3&gt;

&lt;p&gt;In the midst of the very well designed API is what I find to be a somewhat odd design decision. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;*Request&lt;/code&gt; passed to the &lt;code&gt;RoundTripper&lt;/code&gt;, and the &lt;code&gt;*Request&lt;/code&gt; passed to your http &lt;code&gt;Handler&lt;/code&gt; is the same type, but they are really &lt;em&gt;two different things&lt;/em&gt;. The one is an &lt;em&gt;outgoing&lt;/em&gt; request, and the other is an &lt;em&gt;incoming&lt;/em&gt; request.&lt;/p&gt;

&lt;p&gt;While they share are similarities, they are not the entirely the same, and the type has properties that are only relevant for processing incoming requests, such as decoding form data. And there have different &lt;em&gt;rules&lt;/em&gt; determining if a request is valid. &lt;/p&gt;

&lt;p&gt;First, the outgoing request is allowed to have a &lt;code&gt;nil&lt;/code&gt; request body, where the incoming request always have a body. &lt;/p&gt;

&lt;p&gt;Then, the incoming request is a source of a &lt;a href="https://pkg.go.dev/context" rel="noopener noreferrer"&gt;&lt;code&gt;context.Context&lt;/code&gt;&lt;/a&gt;, which I didn't initialise either.&lt;/p&gt;

&lt;p&gt;Fixing the request was easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="n"&gt;TestRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRecorder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nullReader&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;clientReq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;clientReq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;
    &lt;span class="n"&gt;clientReq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trailer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Trailer&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clientReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, the browser itself will create HTTP requests using an instance of the &lt;code&gt;http.Client&lt;/code&gt; provided. Test code can control the client, replacing default HTTP request behaviour with one that calls your HTTP handler, and an outgoing request from the browser is now just a simple function call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding cookie support
&lt;/h2&gt;

&lt;p&gt;Go also provides a cookie jar. So adding cookie support was so simple that giving it a headling was completely overkill.&lt;/p&gt;

&lt;p&gt;The function that constructs an &lt;code&gt;http.Client&lt;/code&gt; communicating with a simple handler is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http/cookiejar"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewHttpClientFromHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cookiejar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cookiejar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TestRoundTripper&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Jar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;cookiejar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing identity provider integration
&lt;/h2&gt;

&lt;p&gt;While this is not a feature yet, it is intended to extend this functionality to support multiple HTTP handlers, simulating different host names.&lt;/p&gt;

&lt;p&gt;This could be valuable in testing an OAuth authentication flow, or sign in using external identity providers. You could create a test HTTP handler simulating the behaviour of an identity provider, and test the flow independently of the external provider.&lt;/p&gt;

&lt;p&gt;In my previous experience, this is often accomplished with a &lt;em&gt;real&lt;/em&gt; identity provider, configured with some test users. But this approach has some drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tests may fail due to a temporary outage of an external service (in the worst case, preventing deployeing a critical)&lt;/li&gt;
&lt;li&gt;Tests may fail due to account lockout.&lt;/li&gt;
&lt;li&gt;Using "real test users" makes test code depend on an external context, i.e., there are factors affecting the outcome of the test that is not specified in the test.&lt;/li&gt;
&lt;li&gt;Developers may not have privileges to administer test users, preventing them from writing the right test, waiting for an administrator to add new users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By mocking an IDP, you gain full control of test environment flow.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; Then you'd need to setup the round tripper to support two hostnames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://app.example.com/&lt;/code&gt; Calls your application http handler&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://idp.example.com/&lt;/code&gt; Calls your mock identity provider web app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you'd still be able to run everything in parallel.&lt;/p&gt;

&lt;p&gt;Intrigued? Check out &lt;a href="https://github.com/gost-dom/browser" rel="noopener noreferrer"&gt;Gost-DOM&lt;/a&gt;. And stay tuned for more nitty-gritty details about how I build a headless browser in Go.&lt;/p&gt;

&lt;p&gt;And please, spread the word. 🙏&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I want to emphasise that this tool is meant to support the majority of tests written to support a TDD flow. That doesn't mean there shouldn't be added more tests after the fact to find critical integration issues, and I certainly recommend having ONE automated tests exercise the login flow if your application integrates with an external identity provider. But I don't want that to be part of the normal development flow. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>go</category>
      <category>tdd</category>
      <category>webdev</category>
      <category>htmx</category>
    </item>
    <item>
      <title>How Gost-DOM is implemented</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Tue, 18 Feb 2025 12:52:31 +0000</pubDate>
      <link>https://dev.to/stroiman/how-gost-dom-is-implemented-48o9</link>
      <guid>https://dev.to/stroiman/how-gost-dom-is-implemented-48o9</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/gost-dom/browser" rel="noopener noreferrer"&gt;Gost-DOM&lt;/a&gt; is headless browser written in Go. It is written with the purpose of supporting a TDD feedback loop while building web applications in Go.&lt;/p&gt;

&lt;p&gt;This article is a short introduction to the the series about the technical solutions. Future articles will go more into details of specific solutions.&lt;/p&gt;

&lt;p&gt;It started as an experiment, but as I progressed I strongly felt that this could become an extremely valuable tool for web development in Go.&lt;/p&gt;

&lt;p&gt;On February 6th, I released version 0.1, signifying that the project had reached a level that it could fulfil minimal use cases. But as the versioning scheme indicates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is a very early prototype&lt;/li&gt;
&lt;li&gt;The API may not be stable. I believe I have found a reasonably good structure, but I am still learning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, the browser now supports minimal HTMX apps, e.g., clicking elements with HTMX handlers, including boosted links, that update history, form submission using HTMX with content swapping.&lt;/p&gt;

&lt;p&gt;Gost was specifically written with HTMX in mind, and it features a &lt;a href="https://v8.dev/" rel="noopener noreferrer"&gt;V8&lt;/a&gt; engine for JavaScript support. Work is in progress to also support &lt;a href="https://github.com/dop251/goja" rel="noopener noreferrer"&gt;Goja&lt;/a&gt;, a pure Go JavaScript engine. Gost has a cookie support, and Gost can bypass the TCP stack for extremely fast test execution. It supports full isolation of test cases with no fuzz, and parallel tests as a result (depending only on &lt;em&gt;your&lt;/em&gt; code). The goal is "ludicrous speed" for web application TDD feedback loop.&lt;/p&gt;

&lt;p&gt;In future articles, I will dive in to specific aspects and solution I have applied during the implementation of Gost-DOM.&lt;/p&gt;

&lt;p&gt;But I invite you to try out &lt;a href="https://github.com/gost-dom/browser" rel="noopener noreferrer"&gt;Gost-DOM&lt;/a&gt;, and spread the word.&lt;/p&gt;

</description>
      <category>go</category>
      <category>tdd</category>
      <category>webdev</category>
      <category>htmx</category>
    </item>
    <item>
      <title>I created a headless browser in Go. Here's what I learned</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Sat, 08 Feb 2025 08:18:33 +0000</pubDate>
      <link>https://dev.to/stroiman/i-created-a-headless-browser-in-go-heres-what-i-learned-64j</link>
      <guid>https://dev.to/stroiman/i-created-a-headless-browser-in-go-heres-what-i-learned-64j</guid>
      <description>&lt;p&gt;I have more then 20 years of web development experience, both using server-side rendering, and React. React is a great framework for advanced UI; but it does bring a lot of complexity compared to SSR.&lt;/p&gt;

&lt;p&gt;When I watched &lt;a href="https://www.youtube.com/watch?v=x7v6SNIgJpE" rel="noopener noreferrer"&gt;The Primeagen's introduction to HTMX&lt;/a&gt;, I had a eureka moment. A tool that enables the same smooth UX as React for the most parts; but with the simplicity of SSR. And when HTMX doesn't cut it, and a hybrid is a very plausible solution. PWAs are not possible, but who really need that? (who are willing to pay for the complexity)&lt;/p&gt;

&lt;p&gt;I started learning HTMX, but as a long time TDD practitioner, I wanted to write tests to provide fast feedback helping drive implementation of behaviour.&lt;/p&gt;

&lt;p&gt;Testing HTTP servers in Go is extremely easy, as an HTTP server is just a function; easily callable from test code. This makes testing the HTTP layer like testing any other part of the Go codebase, supporting mocking of dependencies as necessary.&lt;/p&gt;

&lt;p&gt;But request/response verification of an HTMX test is tightly coupled to implementation details instead of behaviour and responsibility. The application behaviour is a result of a choreography of attributes in the HTML, backend route handlers, and the responses headers and response bodies intended trigger a specific behaviour in the browser.&lt;/p&gt;

&lt;p&gt;I searched for what people used to test HTMX applications, and the answer was: browser automation, mostly playwright.&lt;/p&gt;

&lt;p&gt;Such a tool has so much overhead that it discourages a TDD process. In my mind, there is only one way, a headless browser with an interface in the same language as your backend code, allowing tests to mock dependencies. And there wasn't one for Go.&lt;/p&gt;

&lt;p&gt;So I decided to build my one: &lt;a href="https://github.com/gost-dom/browser" rel="noopener noreferrer"&gt;Gost-DOM&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eliminating the unknowns
&lt;/h2&gt;

&lt;p&gt;To get something like this working, I knew that there were two major uncertainties I had to address.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse HTML into a DOM&lt;/li&gt;
&lt;li&gt;Execute JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started exploring both options, and after just two days, I had an early prototype that was able to parse an HTML page with a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag, and execute it.&lt;/p&gt;

&lt;p&gt;It gave me confidence to believe that I could pull it off. But I hadn't fully grasped the scope of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing HTML
&lt;/h3&gt;

&lt;p&gt;I'm not foreign to language parsers, generating an AST from source code. But HTML parsing has a unique twist. You cannot just exist with a non-zero exit code if the HTML is malformed. It &lt;em&gt;must&lt;/em&gt; generate a DOM; and it's clearly specified how.&lt;/p&gt;

&lt;p&gt;I decided to defer the problem of malformed content for the first prototype, and I quickly had a prototype that could parse a simple document into a DOM. I started implementing specific rules, such as executing scripts when they are mounted. Well, that's about it at the time.&lt;/p&gt;

&lt;p&gt;While I was getting feedback on the actual DOM implementation in the &lt;a href="https://www.reddit.com/r/golang/" rel="noopener noreferrer"&gt;golang subreddit&lt;/a&gt;, a friendly mentioned I might want to consider using the &lt;a href="https://pkg.go.dev/golang.org/x/net/html" rel="noopener noreferrer"&gt;x/net/html package&lt;/a&gt;. This is a an HTML parser that already handles all the weird rules for malformed HTML. &lt;/p&gt;

&lt;p&gt;While the types themselves couldn't work as the DOM in my scenario, and the parser didn't handle the DOM tree construction rules, it did handle the HTML parsing rule, including dealing with malformed HTML.&lt;/p&gt;

&lt;p&gt;I decided to discard my own parser, and parse HTML into the &lt;code&gt;x/net/html&lt;/code&gt; structure, then iterate that tree to build my own.&lt;/p&gt;

&lt;p&gt;This solution has limitations, such as &lt;code&gt;document.write&lt;/code&gt; doesn't work; the input stream has already been consumed when it's executed. But the solution is perfect for the first version of the tool. The priority is to support testing &lt;em&gt;modern web applications&lt;/em&gt;, and the solution allowed me to quickly turn address other problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedding and extending V8
&lt;/h2&gt;

&lt;p&gt;As fun as it would be to write a JavaScript engine, I would never have gotten to the point I am now, had I written my own JavaScript engine. But Go support linking to C code, so embedding V8 was a definite possibility, and I'm confident it has the features needed in a browser context.&lt;/p&gt;

&lt;p&gt;I searching for anything about embedding V8 into Go, and I found more than I was looking for. A project already existed, exposing V8 to Go, a project called &lt;a href="https://github.com/rogchap/v8go" rel="noopener noreferrer"&gt;v8go&lt;/a&gt;. This hadn't been maintained for some time, but  &lt;a href="https://github.com/tommie/v8go" rel="noopener noreferrer"&gt;tommie's fork&lt;/a&gt; had not just been kept up-to-date with latest v8 versions. It automatically pulls the latest version from chromium sources!&lt;/p&gt;

&lt;p&gt;I added tommie's version to my project, but soon learned, that many essential features of V8 wasn't exposed in to Go code. When the DOM itself is implemented in Go, I need objects in V8 to wrap Go objects, and build the prototype hierarchy in Go. All essential V8 features for this use case was not available in v8go.&lt;/p&gt;

&lt;p&gt;I have added these in &lt;a href="https://github.com/stroiman/v8go/tree/go-dom-support" rel="noopener noreferrer"&gt;my own fork&lt;/a&gt;. Most of my changes have been merged to tommie's fork now, a few still only exist in my fork.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning CGo
&lt;/h3&gt;

&lt;p&gt;To add the functionality, I needed to learn the V8 API, as well as CGo, the part of Go that allows calling C code, and calling back from C code.&lt;/p&gt;

&lt;p&gt;As Rogchap, the original author of v8go, had already creates the framework, I could start with little knowledge, and copy/paste his code, adapting to the new V8 API functions that needed to be exposed. But the header files had a weird structure, which I realised was due to another problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go and C++ is like oil and water
&lt;/h3&gt;

&lt;p&gt;Go cannot call C++ code, just C code. Rogchap had implemented a solution using preprocessor directives. When header files are compiled as part of the Go compilation, it sees C compatible code, and when they are part of the C++ compilation, it appears as C++ classes.&lt;/p&gt;

&lt;p&gt;The Go types only store pointers, so the types the the Go compiler see doesn't need to be complete. Go just need to know that they are pointer types. The following snippet of header file shows the general approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifdef __cplusplus
&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;v8&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// Forward declaration, the header file only uses pointers to this, so &lt;/span&gt;
&lt;span class="c1"&gt;// the header file doesn't actually _need_ to include v8's "isolate.h"&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Define a v8Isolate type, which C++ code sees as the v8 Isolate class&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="n"&gt;v8&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Isolate&lt;/span&gt; &lt;span class="n"&gt;v8Isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// The following functions should have C-calling conventions.&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;&lt;span class="c1"&gt;// For C code, v8Isolate is defined as a struct&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;v8Isolate&lt;/span&gt; &lt;span class="n"&gt;v8Isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
&lt;span class="c1"&gt;// There is a C-compatible function, NewIsolate() which returns a pointer to&lt;/span&gt;
&lt;span class="c1"&gt;// a V8 Isolate. C++ compilation will know what it is, the v8::Isolate class.&lt;/span&gt;
&lt;span class="c1"&gt;// Go code will just know that it's a pointer, and that's all that Go code&lt;/span&gt;
&lt;span class="c1"&gt;// needs to know. It's still a nominal type, so Go will still check that you &lt;/span&gt;
&lt;span class="c1"&gt;// use the right types.&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="n"&gt;v8Isolate&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NewIsolate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#ifdef __cplusplus
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// extern "C"&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can safely say, if Rogchap hadn't created this, my project wouldn't have had a V8 engine. I have written C++ and assembler code, so I understand the fundamentals of C compilation and linking, as well as C++ scoping rules, and the use of smart pointers for resource management; which V8 relies heavily on. But it's 20 years ago since I last wrote C++. I don't think I would have discovered this pattern on my own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing Go objects to C
&lt;/h3&gt;

&lt;p&gt;In order for an object in JavaScript to wrap an object in the host the V8 object needs to keep some reference to the host object.&lt;/p&gt;

&lt;p&gt;For this purpose, V8 has the concept of an &lt;em&gt;external&lt;/em&gt; value which can hold a generic pointer. But Go pointers aren't safe to pass to C for this usage. Go's garbage collector can move objects in memory, updating pointer variables accordingly. So any pointer held on to by C code would become invalid.&lt;/p&gt;

&lt;p&gt;Go provides two solutions for this. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/runtime#Pinner" rel="noopener noreferrer"&gt;&lt;code&gt;runtime.Pinner&lt;/code&gt;&lt;/a&gt; can prevent an object from being moved by the garbage collector, making pointers "safe" to pass to C code.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/runtime/cgo#Handle" rel="noopener noreferrer"&gt;&lt;code&gt;cgo.Handle&lt;/code&gt;&lt;/a&gt; creates an integer (&lt;code&gt;uintptr&lt;/code&gt;) handle for the object. Internally, handles are generated by an atomically incrementing number, and a synchronised map convert a handle to a pointer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, both mechanisms prevent the object from being garbage collected, so you need to explicitly cleanup the object after use.&lt;/p&gt;

&lt;p&gt;V8go didn't use any of these tho mechanisms, but had its own system which works &lt;em&gt;exactly like&lt;/em&gt; the cgo handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  A mistake was made
&lt;/h3&gt;

&lt;p&gt;It took some time before I realised I had made a mistake. Before I started, I had already made a technical decision based on my existing knowledge: Integrate V8. It never occurred to me that there could be other options. There were two alternate options I should have considered, and one I should have picked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SpiderMonkey, the JavaScript engine for Firefox.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dop251/goja" rel="noopener noreferrer"&gt;Goja&lt;/a&gt;, a JavaScript engine implemented in Go.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Could spider monkey have been a better choice than V8? V8 isn't exactly easy to use outside C++ due to it's heavy reliance on C++ style resource management.&lt;/p&gt;

&lt;p&gt;But Goja! A pure JavaScript engine! If I had known about this up front, I have used this instead.&lt;/p&gt;

&lt;p&gt;I started adding support for Goja as an alternate engine. It's not ready yet, but when it catches up with the V8 integration, I will switch to Goja as the default engine, eliminating the need for CGo.&lt;/p&gt;

&lt;p&gt;Integrating with Goja is significantly easier than integrating with V8&lt;/p&gt;

&lt;p&gt;V8 support will not go away, as I should be able to safely rely on this being updated with latest JavaScript features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing an Object-Oriented API
&lt;/h2&gt;

&lt;p&gt;To build a browser, you have to implement the DOM specification. And that specification is very 90s style object-oriented in its design. It is full of of circular references; and subclasses overriding behaviour of superclasses.&lt;/p&gt;

&lt;p&gt;Everything in the DOM is a node, and you can append children to nodes. Children all know about their parent. Specialised nodes could be &lt;code&gt;Text&lt;/code&gt;,  &lt;code&gt;Comment&lt;/code&gt;,  and &lt;code&gt;Element&lt;/code&gt;, which again has an &lt;code&gt;HTMLElement&lt;/code&gt; subclass, further broken down into &lt;code&gt;HTMLBodyElement&lt;/code&gt;, &lt;code&gt;HTMLFormElement&lt;/code&gt;, &lt;code&gt;HTMLAnchorElement&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;A first solution could be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;
    &lt;span class="c"&gt;// Unexported; we need to call it to maintain the tree, but client&lt;/span&gt;
    &lt;span class="c"&gt;// code shouldnt.&lt;/span&gt;
    &lt;span class="n"&gt;setParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Element&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;
    &lt;span class="c"&gt;// methods that exist on element&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;node&lt;/span&gt;
    &lt;span class="n"&gt;tagName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;setParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this code doesn't work.&lt;/p&gt;

&lt;p&gt;The type &lt;code&gt;*node&lt;/code&gt; is a valid &lt;code&gt;Node&lt;/code&gt; implementation, which makes &lt;code&gt;*element&lt;/code&gt; one too as it embeds &lt;code&gt;node&lt;/code&gt;. But when you call &lt;code&gt;AppendChild&lt;/code&gt; on an element, it is a method on the embedded &lt;code&gt;node&lt;/code&gt; that is executed. This method updates the parent of the new node to itself, the receiver value. Which is the embedded node, not the element.&lt;/p&gt;

&lt;p&gt;So the DOM API, which is beyond my control, isn't a good fit for Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solution. Was it the right one?
&lt;/h3&gt;

&lt;p&gt;The solution I chose is to add a new method, &lt;code&gt;SetSelf&lt;/code&gt; to &lt;code&gt;Node&lt;/code&gt;, and single type that embeds a &lt;code&gt;node&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; call it with a reference to itself as a &lt;code&gt;Node&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;   &lt;span class="n"&gt;Node&lt;/span&gt;
    &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SetSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AppendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Element&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tagName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSelf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The element tells the embedded node what it's own &lt;code&gt;Node&lt;/code&gt; implementation is, pointing to the element, not the embedded node. So for an element, the &lt;code&gt;Node&lt;/code&gt; passed to &lt;code&gt;SetSelf&lt;/code&gt; can still be cast back to &lt;code&gt;Element&lt;/code&gt;, and any method on &lt;code&gt;Node&lt;/code&gt;, where &lt;code&gt;*element&lt;/code&gt; has provided its own implementation, it is now the method on &lt;code&gt;*element&lt;/code&gt; which is called, not the one on &lt;code&gt;*node&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm not really happy with this. The necessity of the call is not very obvious from the code; and if a specialised node type forget to call it, the system will fail in ways that does little to reveal why.&lt;/p&gt;

&lt;p&gt;The And as I don't want to have to implement every HTML element in the same package, the method also needs to be exported (what other languages call public).&lt;/p&gt;

&lt;p&gt;I am wondering if a &lt;a href="https://en.wikipedia.org/wiki/Strategy_pattern" rel="noopener noreferrer"&gt;Strategy Pattern&lt;/a&gt; would have been a better solution, but that presents other challenges, like methods and attributes on specialised elements are exposed. E.g., a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element is represented by an &lt;code&gt;HTMLFormElement&lt;/code&gt; and it has a specific properties and methods, e.g.,  a &lt;code&gt;method&lt;/code&gt; property, and a &lt;code&gt;requestSubmit&lt;/code&gt; method. And the &lt;code&gt;HTMLTemplateElement&lt;/code&gt; hides all its children in a document fragment, accessed through a &lt;code&gt;content&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;But so far, the code works as intended, and perhaps a different solution will present later?&lt;/p&gt;

&lt;p&gt;p.s. If you have actual experience building a rendering engine, I'd like to hear from you, about your experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  "You Know Nothing"
&lt;/h2&gt;

&lt;p&gt;I have been in the software development industry for 25 years, and most of that has been web development. I am surprised at how much I didn't know.&lt;/p&gt;

&lt;p&gt;There are special elements with special behaviour. For example, the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; which doesn't have any children. Instead, it's children in the HTML are added to a document fragment accessible in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement/content" rel="noopener noreferrer"&gt;&lt;code&gt;content&lt;/code&gt;&lt;/a&gt; property. Yet they are rendered when reading &lt;code&gt;outerHTML&lt;/code&gt;. This means that the &lt;code&gt;HTMLTemplateElement&lt;/code&gt; needs to override &lt;code&gt;outerHTML&lt;/code&gt; (object-oriented API).&lt;/p&gt;

&lt;h3&gt;
  
  
  Attributes aren't just attributes
&lt;/h3&gt;

&lt;p&gt;The first good half of my career was server-side rendering of HTML (really the only option back then). Later, I have written a lot of React using the JSX syntax, which is almost indistinguishable from HTML.&lt;/p&gt;

&lt;p&gt;So I was rather surprised, when I learned that attributes on elements aren't just attributes. After all, for both SSR and React, I write attributes in an HTML-like style, not revealing any difference, except for a few with different names. Yet there are two types of attributes: &lt;em&gt;data attributes&lt;/em&gt; and &lt;em&gt;idl attributes&lt;/em&gt;. The attributes in the HTML are data attributes; an accessible in the DOM using &lt;code&gt;getAttribute&lt;/code&gt; and &lt;code&gt;setAttribute&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;IDL Attributes&lt;/em&gt; are exposed as properties on the JavaScript element objects. They often reflect an identically named data attribute, but the can implement specific behaviour.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;HTMLFormElement&lt;/code&gt; has a &lt;code&gt;method&lt;/code&gt; IDL attribute. This will always return either &lt;code&gt;"get"&lt;/code&gt; or &lt;code&gt;"post"&lt;/code&gt;, no matter the value of the data attribute. The default value is &lt;code&gt;"get"&lt;/code&gt;, and any invalid value will result in &lt;code&gt;"get"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// null&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;
&lt;span class="c1"&gt;// "get"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// "invalid"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;
&lt;span class="c1"&gt;// "get"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// "post"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;
&lt;span class="c1"&gt;// "post"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pOsT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// "pOsT"&lt;/span&gt;
&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;
&lt;span class="c1"&gt;// "post"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementing IDL attributes isn't too problematic; they often expose behaviour I need anyway; e.g., both the &lt;code&gt;method&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; IDL attributes on a form implement logic necessary for form posting anyway.&lt;/p&gt;

&lt;p&gt;But I was rather surprised I didn't know of this distinction.&lt;/p&gt;

&lt;p&gt;Some IDL attributes also have different names then their data attribute counterpart, for example all &lt;code&gt;aria-&lt;/code&gt; data attributes have camel-cased IDL attribute names, e.g., &lt;code&gt;arialLabel&lt;/code&gt;. And the data attributes &lt;code&gt;for&lt;/code&gt; and &lt;code&gt;class&lt;/code&gt; are reserved JavaScript words, so the corresponding IDL attributes are named &lt;code&gt;htmlFor&lt;/code&gt; and &lt;code&gt;className&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;React developers will undoubtedly recognise the latter two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where's the event loop?
&lt;/h3&gt;

&lt;p&gt;I was browsing through the V8 API documentation, searching for the place to register a callback handling errors caused by &lt;code&gt;setTimeout&lt;/code&gt; or &lt;code&gt;setInterval&lt;/code&gt; callbacks; but nothing like that existed.&lt;/p&gt;

&lt;p&gt;That's because those functions aren't in V8 at all because they are actually not part of the ECMAScript specification.&lt;/p&gt;

&lt;p&gt;Browsers and node.js just happen to implement these functions. I did know that the two actually don't have the same interface; they differ in return type of the returned handle. The handle is a &lt;code&gt;number&lt;/code&gt; in a browser, while it's an object type in node.&lt;/p&gt;

&lt;p&gt;In hindsight, that alone should have been the dead giveaway that it's not implemented by V8. If chrome and node.js both use V8, and a global function returns different types, then it's probably not supplied by V8 itself.&lt;/p&gt;

&lt;p&gt;So to write a headless browser, you need to write an event loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autogenerating Code
&lt;/h2&gt;

&lt;p&gt;Much of the behaviour of the DOM is specified by Web IDL specifications. After the first couple of JavaScript wrappers were written by hand, I started to investigate the possibility of autogenerating this code. I found &lt;a href="https://github.com/w3c/webref" rel="noopener noreferrer"&gt;webref&lt;/a&gt;, repository that contains a collection of all the IDL files, and is automatically updated. I added this as a submodule to start consuming those files.&lt;/p&gt;

&lt;p&gt;Over time that process led to two new separate Go packages that have made available separately, as they have general purpose use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/gost-dom/webref" rel="noopener noreferrer"&gt;gost-dom/webref&lt;/a&gt; - A package that exposes a subset of the IDL data as native Go types.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/gost-dom/generators" rel="noopener noreferrer"&gt;gost-dom/generators&lt;/a&gt; - Helper types for code generation. This is a layer on top of &lt;a href="https://github.com/dave/jennifer" rel="noopener noreferrer"&gt;jennifer&lt;/a&gt;, providing an interface that lends itself better to composition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cutting corners
&lt;/h2&gt;

&lt;p&gt;It's a long tradition in JavaScript to use polyfills to backport new browser features to older browsers. The same polyfills can also be used to quickly add functionality to Gost, and many already exists. Currently, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/XPathEvaluator" rel="noopener noreferrer"&gt;XPathEvaluator&lt;/a&gt; is a pure JavaScript using slightly modified code from &lt;a href="https://github.com/jsdom/jsdom" rel="noopener noreferrer"&gt;jsdom&lt;/a&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Many smaller functions, and constants, are only implemented in JavaScript code. For example, node type constants are in JavaScript space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEMENT_NODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ATTRIBUTE_NODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEXT_NODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CDATA_SECTION_NODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// etc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For CSS selector queries, I use a Go package, &lt;a href="https://github.com/ericchiang/css" rel="noopener noreferrer"&gt;CSS&lt;/a&gt;. This works with &lt;code&gt;x/net/html&lt;/code&gt;, the types that were used during HTML parsing. So when performing a CSS query, I recreate an "x" HTML tree from the Gost DOM, run the query on that tree, and translate the result back to the original elements.&lt;/p&gt;

&lt;p&gt;Unfortunately, the library doesn't support checking if an element itself matches a CSS selector. So eventually I had to create my own matcher for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/matches" rel="noopener noreferrer"&gt;Element.matches&lt;/a&gt;. But that is a minimal implementation, only used here, and only supporting the selectors that HTMX uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The really cool part!
&lt;/h2&gt;

&lt;p&gt;As mentioned in the beginning, testing an HTTP server in Go, is just a matter of calling an HTTP handler function.&lt;/p&gt;

&lt;p&gt;A headless browser in Go can do the same. From day one, I added the ability to bypass the TCP layer completely, connecting directly to the http handler implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFromHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RootHttpHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// The host is ignored, but cookies don't work if it's not there.&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:1234/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"login-link"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no overhead of the TCP stack, no hassle of managing startup and shutdown. You can run all tests in parallel with full isolation; of your own code supports isolation. Each browser has it's own isolated V8 instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state of Gost
&lt;/h2&gt;

&lt;p&gt;I recently released version 0.1. This release signifies that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This has very limited functionality, but enough to support some real usage patterns.&lt;/li&gt;
&lt;li&gt;The API is not guaranteed to be stable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it is now in the state where you it covers basic scenarios like a "login flow"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to the "index".&lt;/li&gt;
&lt;li&gt;Click an HTMX boosted link that should navigate to a page requiring authentication, responding with a &lt;code&gt;hx-push-url&lt;/code&gt; header, and the login page&lt;/li&gt;
&lt;li&gt;Verify that the current location is the login page, and the history has a new entry.&lt;/li&gt;
&lt;li&gt;Fill out the form (setting the &lt;code&gt;"value"&lt;/code&gt; &lt;em&gt;data attribute&lt;/em&gt; on the input fields).&lt;/li&gt;
&lt;li&gt;Click the form's submit button (&lt;code&gt;&amp;lt;button type="submit"&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt;, both works).&lt;/li&gt;
&lt;li&gt;Verify that the user is now redirected to the page they were trying to access, the location is updated, and the history has a new entry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any JavaScript/DOM feature that is not directly affected by this flow isn't supported yet. E.g., there is a &lt;code&gt;setTimeout&lt;/code&gt; function, but it disregards the timeout value; the callback is scheduled for immediate execution. There isn't yet a &lt;code&gt;setInterval&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's up.
&lt;/h3&gt;

&lt;p&gt;The currently planned next feature is to have a proper event loop supporting &lt;code&gt;setInterval&lt;/code&gt;, as well as the &lt;code&gt;clear&lt;/code&gt; function. &lt;em&gt;And to support time travel&lt;/em&gt; in order to test behaviour that requires time to pass, without actually having to wait in the test. Very much like &lt;a href="https://www.npmjs.com/package/@sinonjs/fake-timers" rel="noopener noreferrer"&gt;&lt;del&gt;lolex&lt;/del&gt; sinon fake timers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But that shouldn't take too long to implement. I'll just inject fake timers on the JavaScript side.&lt;/p&gt;

&lt;p&gt;But user feedback can to a large degree change priorities. If real users struggle with specific problems due to lack of support, that will be a natural focus of attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Please go use it
&lt;/h2&gt;

&lt;p&gt;I believe that this would be an extremely useful tool for anyone using Go for web development with SSR and JavaScript, in particular HTMX. And as the popularity of this stack increases, this tool becomes even more relevant.&lt;/p&gt;

&lt;p&gt;In theory, this should work with React apps as well; but the current focus is HTMX support.&lt;/p&gt;

&lt;p&gt;I invite everyone to try it out. Use it for the simple cases, as explained above. &lt;/p&gt;

&lt;p&gt;And please, spread the word.&lt;/p&gt;

&lt;p&gt;You can find Gost &lt;a href="https://github.com/gost-dom/browser" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;jsdom doesn't have an &lt;code&gt;XPathEvaluator&lt;/code&gt; class, but it does implement the global xpath evaluation functions. My adaptation was to write a class using those functions. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>go</category>
      <category>tdd</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Create an auto-merging workflow on Github</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Mon, 06 Jan 2025 12:16:09 +0000</pubDate>
      <link>https://dev.to/stroiman/create-an-auto-merging-workflow-on-github-joj</link>
      <guid>https://dev.to/stroiman/create-an-auto-merging-workflow-on-github-joj</guid>
      <description>&lt;p&gt;How do you prevent bad commits from entering the main branch?&lt;/p&gt;

&lt;p&gt;You can add a workflow that builds and runs your test suite. This will notify you immediately when this has happened. But the bad commits are already there, possibly blocking other developers. Or worse, preventing a crucial hotfix from reaching production.&lt;/p&gt;

&lt;p&gt;You can submit a pull request, a popular popular way for teams to work. But pull requests are intended to handle the process for feedback of proposed changes. For single-developer repos, or teams that don't need this feedback process, using pull requests just to prevent broken builds from entering the default branch easily overcomplicates things.&lt;/p&gt;

&lt;p&gt;A much simpler process is to just push to a different branch, have a let github automatically merge the changes to your main branch &lt;em&gt;if verification succeeds&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This article shows how to setup such a workflow that runs &lt;em&gt;almost&lt;/em&gt; seamlessly, and how to deal with a few  challenges along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the permissions
&lt;/h2&gt;

&lt;p&gt;This setup requires a github workflow that pushes to your repository. In order for this to work, you need to set the proper permissions for the project.&lt;/p&gt;

&lt;p&gt;Go to the settings page for your project, and open the "Actions &amp;gt; General" settings, where you must configure "Workflow permissions" to "Read and write permissions".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3drr0ztmragctya6ouu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq3drr0ztmragctya6ouu.png" alt="Screenshot showing the workflow permissions being set to read and write premissions" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once this is done, workflow actions can push to your repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create/update a verification workflow
&lt;/h2&gt;

&lt;p&gt;You must have a verification workflow. I assume that you already have an existing workflow, and that it has a &lt;code&gt;push&lt;/code&gt; trigger. &lt;/p&gt;

&lt;p&gt;Normally, the trigger is not set to run on all branches, so you need to add the branch name to use. Here it's called &lt;code&gt;auto-merge&lt;/code&gt;. If you don't already have a &lt;code&gt;push&lt;/code&gt; trigger, be sure to add it.&lt;/p&gt;

&lt;p&gt;Remember the name of the workflow, here it's &lt;code&gt;Build&lt;/code&gt;. It is needed in the next step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/build.yaml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto-merge"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a team setting, each developer could have their own branch, and you can use a wildcard to react to them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  About github workflows
&lt;/h3&gt;

&lt;p&gt;If you are new to workflows, here are some fundamentals.&lt;/p&gt;

&lt;p&gt;A github workflow is started from a &lt;em&gt;trigger&lt;/em&gt;. A common configuration is to use both the &lt;code&gt;push&lt;/code&gt; trigger, to run the workflow when there has been pushed to a branch, and the &lt;code&gt;pull_request&lt;/code&gt; trigger, which obviously triggers when a pull request is created, or new code pushed to the pull request.&lt;/p&gt;

&lt;p&gt;There are many kinds of triggers, including triggers that are completely unrelated to code. E.g., I used a scheduled job trigger to renew server certificates from a github workflow.&lt;/p&gt;

&lt;p&gt;A verification workflow would normally use &lt;code&gt;actions/checkout&lt;/code&gt; to fetch the code from git. For a &lt;code&gt;push&lt;/code&gt; trigger, the branch will by default be checked out. For a &lt;code&gt;pull_request&lt;/code&gt; trigger, a &lt;em&gt;merge commit&lt;/em&gt; will be checked out, i.e. it is the result of a merge with the default branch that is verified by the workflow.&lt;/p&gt;

&lt;p&gt;A common default default is to have both &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;pull_request&lt;/code&gt; triggers on the default branch, here &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/build.yaml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto-merge"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;


&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and test the code&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu_latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Uses can use pre-made actions. &lt;/span&gt;
    &lt;span class="c1"&gt;# actions/checkout will fetch your code.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
    &lt;span class="c1"&gt;# Frameworks can often be configured using other actions.&lt;/span&gt;
    &lt;span class="c1"&gt;# Github have good starting points for most project types.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and test&lt;/span&gt;
      &lt;span class="c1"&gt;# Make sure you run the steps necessary. &lt;/span&gt;
      &lt;span class="c1"&gt;# Github have good starting points for most project types.&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./build-and-test.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a new workflow in the &lt;em&gt;default&lt;/em&gt; branch.
&lt;/h2&gt;

&lt;p&gt;The new workflow uses the &lt;a href="https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run" rel="noopener noreferrer"&gt;&lt;code&gt;workflow_run&lt;/code&gt;&lt;/a&gt; trigger, a trigger that can react to events of another workflow.&lt;/p&gt;

&lt;p&gt;This workflow is not associated with a branch, it points to another workflow. Because of that, it is &lt;em&gt;global&lt;/em&gt; to the github project and must exist in the &lt;em&gt;default&lt;/em&gt; branch; typically named &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The auto-merge workflow should be run when the verification workflow is  &lt;code&gt;completed&lt;/code&gt;, and the workflow was executed on the &lt;code&gt;auto-merge&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/auto-merge.yaml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-merge&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;auto-merge&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;completed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So while the workflow with a &lt;code&gt;workflow_run&lt;/code&gt; trigger workflows on a project level, it can still filter on the branches that were the trigger a verification workflow run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can use wildcards in your branch names if you have multiple auto-merge branches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a job
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;job&lt;/em&gt; does the actual work. While this is triggered on a &lt;em&gt;completed&lt;/em&gt; verification workflow, a completed workflow can still have &lt;em&gt;failed&lt;/em&gt;. To handle that, add a condition to check the outcome of the completed workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;on-success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.workflow_run.conclusion == 'success' }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fetch the code
&lt;/h3&gt;

&lt;p&gt;To fetch the code, we use the &lt;code&gt;actions/checkout&lt;/code&gt; action.&lt;/p&gt;

&lt;p&gt;The first unexpected issue is that the action checks out the default branch, not the &lt;code&gt;auto-merge&lt;/code&gt; branch; which was the original source of a trigger. While a &lt;code&gt;push&lt;/code&gt; trigger will by default check out the branch that was updated, the &lt;code&gt;workflow_run&lt;/code&gt; trigger does not. We must check out the right branch in the workflow ourselves.&lt;/p&gt;

&lt;p&gt;The branch name exists as data on the &lt;a href="https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run" rel="noopener noreferrer"&gt;associated event&lt;/a&gt; of the trigger. This contains information about the completed workflow, including the name of the branch that triggered the first workflow. This value is found in the variable &lt;code&gt;github.event.workflow_run.head_branch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For a single auto-merging branch, we could just have duplicated the branch name, but for multiple branches, it's necessary to read this value from the event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shallow Clones
&lt;/h3&gt;

&lt;p&gt;The next issue is that by default, the checkout action creates a shallow clone, i.e., there is no history. You cannot push to a branch, if you don't locally have all commits from, and including, the head of the target branch to your branch.&lt;/p&gt;

&lt;p&gt;The easiest solution is to add &lt;code&gt;fetch-depth: 0&lt;/code&gt; to the action. This pulls the full history, problem solved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.workflow_run.head_branch }}&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For large repositories with a lot of history, or large binary files in the history, this can increase the runtime significantly. But there are ways to deal with this problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Smarter handling of shallow clones
&lt;/h3&gt;

&lt;p&gt;Instead of fetching full history, we can fetch just enough history to be able to push. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is much more tricky, so for smaller repositories, the previous method would be advisable. Particularly, if you/the team don't feel comfortable with shell scripting.&lt;/p&gt;

&lt;p&gt;The changes to make compared to the previous simple version are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Remove the &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;fetch-depth&lt;/code&gt; options.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;git remote set-branches ...&lt;/code&gt;  to tell git there is a different remote branch we want to use.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;git fetch ...&lt;/code&gt; to fetch the relevant commits, i.e. just enough shallow history, to be able to push the changes.&lt;/li&gt;
&lt;li&gt;Checkout the source branch locally.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tell git we want the `auto-merge` branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git remote set-branches --add origin ${{ github.event.workflow_run.head_branch }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fetch the target branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git fetch --shallow-since="`git show --no-patch --format=%ci HEAD`"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout target branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git checkout ${{ github.event.workflow_run.head_branch }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a more detailed explanation of the changes.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Remove &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;fetch-depth&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Because we need to have all commits in the source branch not reachable from the target branch (default branch), we need the target branch in our working copy. The simplest solution is that start the process with the target branch checked out.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Add new remote branch
&lt;/h4&gt;

&lt;p&gt;In a shallow clone, git doesn't fetch all remote branches, only the target branch. The following command tells git explicitly that this is a branch we want to work with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote set-branches &lt;span class="nt"&gt;--add&lt;/span&gt; origin &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ ... .head_branch &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, for a single branch, you &lt;em&gt;could&lt;/em&gt; duplicate the branch name, but it's so simple to avoid it.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Fetch the remote branch.
&lt;/h4&gt;

&lt;p&gt;With the remote branch added, we can fetch the branch using &lt;code&gt;git fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a shallow clone, &lt;code&gt;fetch&lt;/code&gt; will fetch commits that are &lt;em&gt;after&lt;/em&gt; the current branch, i.e., it will fetch all the commits that are new in the &lt;code&gt;auto-merge&lt;/code&gt; branch. But if the target branch  is &lt;em&gt;ahead&lt;/em&gt; of the source branch, i.e., the default branch contains commits not yet in the auto-merge branch, &lt;code&gt;git fetch&lt;/code&gt; fetches the entire history, defeating the purpose of the shallow clone to begin with.&lt;/p&gt;

&lt;p&gt;This scenario is less likely for a single-developer setup, but very likely to happen occasionally in a team setup where team members use individual auto-merge branches.&lt;/p&gt;

&lt;p&gt;To keep the clone shallow, the command option &lt;code&gt;--shallow-since=""&lt;/code&gt; is used to not fetch commits older than the current &lt;code&gt;HEAD&lt;/code&gt; (which is the default branch). The commit timestamp for &lt;code&gt;HEAD&lt;/code&gt; is found using &lt;code&gt;git show --no-patch --format=%ci HEAD&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch &lt;span class="nt"&gt;--shallow-since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;git show &lt;span class="nt"&gt;--no-patch&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%ci HEAD&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backticks here is a special shell feature that executes the &lt;code&gt;git show ...&lt;/code&gt; command, and uses the textual output as input for the command, here for the arguments for the &lt;code&gt;git fetch&lt;/code&gt; command&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fetch&lt;/code&gt; will now fetch exactly all the commits that are necessary for the branch to be pushed if possible.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Check out the source branch
&lt;/h4&gt;

&lt;p&gt;How we have &lt;em&gt;just enough&lt;/em&gt; commits in the local database to push. We checkout the branch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ github.event.workflow_run.head_branch &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Push
&lt;/h3&gt;

&lt;p&gt;Both the simple, and the shallow clone workflow variations, have left us with the source branch &lt;br&gt;
checked out. All that is left is to push it to the remote target branch, i.e. push &lt;code&gt;auto-merge&lt;/code&gt; to &lt;code&gt;main&lt;/code&gt; in this example. Here &lt;code&gt;HEAD&lt;/code&gt; is used, so the script doesn't need to know what the branch actually is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;on-success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push to main&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git push origin HEAD:main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, git will only perform a fast-forward merge on push. So the workflow will fail if there are new commits on the target branch. In this case, you need to deal with the conflict, by either merging or rebasing your changes off the new master; just as you normally would.&lt;/p&gt;

&lt;p&gt;The error could be one of two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The push failed because it wasn't a fast-forward.&lt;/li&gt;
&lt;li&gt;The push failed because you didn't have the remote target &lt;code&gt;HEAD&lt;/code&gt; in your local git database. This was because they were not fetched as they were committed earlier than the target &lt;code&gt;HEAD&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No matter which, the cause is still the same, there are new commits in the default branch not reachable from the HEAD of your local branch.&lt;/p&gt;

&lt;p&gt;There is another potential error, you have rewritten the commit time in the history. That shouldn't be a normal workflow, so this workflow is not designed to handle that.&lt;/p&gt;

&lt;h3&gt;
  
  
  The full workflow file
&lt;/h3&gt;

&lt;p&gt;This is the full workflow file. Adapt the following to your own workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The verification workflow is named &lt;code&gt;Build&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The branch to run on is named &lt;code&gt;auto-merge&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The default branch is named &lt;code&gt;main&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/auto-merge.yaml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-merge&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Build&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;auto-merge&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;completed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;on-success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.workflow_run.conclusion == 'success' }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tell git we want the `auto-merge` branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git remote set-branches --add origin ${{ github.event.workflow_run.head_branch }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Fetch the target branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git fetch --shallow-since="`git show --no-patch --format=%ci HEAD`"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout target branch&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git checkout ${{ github.event.workflow_run.head_branch }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push current head to main&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git push -v origin HEAD:main&lt;/span&gt;


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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pushing to the right branch locally
&lt;/h2&gt;

&lt;p&gt;You can push directly to the branch from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git push origin HEAD:auto-merge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the remote is assumed to be named &lt;code&gt;origin&lt;/code&gt;, the default for most workflows. But this can be simplified, so you just need to perform a normal &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To set that up, you need to make some changes in your local git repository configuration, found in the &lt;code&gt;.git/config&lt;/code&gt; file of your working directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  First sub-optimal solution
&lt;/h3&gt;

&lt;p&gt;By adding a &lt;code&gt;push&lt;/code&gt; refspec for the remote, you can configure git to push to a different remote branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .git/config
&lt;/span&gt;&lt;span class="nn"&gt;[remote "origin"]&lt;/span&gt;
    &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;git@github.com:username/repository.git&lt;/span&gt;
    &lt;span class="py"&gt;fetch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;+refs/heads/*:refs/remotes/origin/*&lt;/span&gt;
    &lt;span class="py"&gt;push&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main:refs/heads/auto-merge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setting, when you &lt;em&gt;pull&lt;/em&gt; the &lt;code&gt;main&lt;/code&gt; branch from your local working directory, you get the &lt;code&gt;main&lt;/code&gt; branch from the remote repository, but when you &lt;em&gt;push&lt;/em&gt; the &lt;code&gt;main&lt;/code&gt; branch, it will be pushed to the &lt;code&gt;auto-merge&lt;/code&gt; branch on the remote. If your changes are good, your team mates will quickly get them when they pull.&lt;/p&gt;

&lt;p&gt;Using this setup, you can work on the main branch locally exactly the same way you would normally.&lt;/p&gt;

&lt;p&gt;But this also changes the default behaviour for other branches.&lt;/p&gt;

&lt;h3&gt;
  
  
  A less intrusive configuration
&lt;/h3&gt;

&lt;p&gt;A solution to not break default behaviour for other branches is to create a new &lt;em&gt;remote&lt;/em&gt; with the same &lt;code&gt;url&lt;/code&gt;. Then you can configure the default to use this remote &lt;em&gt;when pushing&lt;/em&gt;. Now other branches are not affected by the change to the push configuration, only the default branch has different behaviour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# .git/config
&lt;/span&gt;&lt;span class="nn"&gt;[remote "origin"]&lt;/span&gt;
    &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;git@github.com:username/repository.git&lt;/span&gt;
    &lt;span class="py"&gt;fetch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;+refs/heads/*:refs/remotes/origin/*&lt;/span&gt;
&lt;span class="nn"&gt;[remote "origin-main"]&lt;/span&gt;
    &lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;git@github.com:username/repository.git&lt;/span&gt;
    &lt;span class="py"&gt;fetch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;+refs/heads/*:refs/remotes/origin/*&lt;/span&gt;
    &lt;span class="py"&gt;push&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main:refs/remotes/origin/auto-merge&lt;/span&gt;
&lt;span class="nn"&gt;[branch "main"]&lt;/span&gt;
    &lt;span class="py"&gt;remote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;origin&lt;/span&gt;
    &lt;span class="py"&gt;pushRemote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;origin-main&lt;/span&gt;
    &lt;span class="py"&gt;merge&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The local workflow
&lt;/h2&gt;

&lt;p&gt;With everything setup, your workflow should looks remarkably familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git pull &lt;span class="c"&gt;# or git pull --rebase&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"Change 1"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"Change 2"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"Change 3"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git push
&lt;span class="c"&gt;# In case of a conflict&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git pull &lt;span class="c"&gt;# or git pull --rebase&lt;/span&gt;
&lt;span class="c"&gt;# Fix merge conflicts&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only differences are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is the delay of the build before you know what the outcome is.&lt;/li&gt;
&lt;li&gt;You local branch will appear to be &lt;em&gt;ahead&lt;/em&gt; of the default branch after a push, until you fetch again after a successful build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But commits failing the verification will not appear in the main branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The git configuration is local
&lt;/h2&gt;

&lt;p&gt;Remember, the repository configuration &lt;em&gt;is local&lt;/em&gt;, i.e., it exists only on the local machine. Every developer need to set the configuration on every computer they use.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's a simple solution to a simple problem
&lt;/h2&gt;

&lt;p&gt;This setup will not prohibit bad commits from reaching the default branch. Developers can still push to directly to the default branch.&lt;/p&gt;

&lt;p&gt;For a team project, every developer needs configure their git configuration to push to an &lt;em&gt;auto-merge branch&lt;/em&gt;, requiring uncommon initial custom configuration.&lt;/p&gt;

&lt;p&gt;But for those projects that don't need complicated processes, this small change can help prevent bad commits from reaching the default branch in a completely non-intrusive way.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Go-DOM - 1st major milestone</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Mon, 18 Nov 2024 07:38:32 +0000</pubDate>
      <link>https://dev.to/stroiman/go-dom-1st-major-milestone-1005</link>
      <guid>https://dev.to/stroiman/go-dom-1st-major-milestone-1005</guid>
      <description>&lt;p&gt;After just under 2 weeks of work; I finally reached the first major milestone for &lt;a href="https://github.com/stroiman/go-dom" rel="noopener noreferrer"&gt;Go-DOM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, the browser will &lt;strong&gt;download and execute remote JavaScript&lt;/strong&gt; when building the DOM tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief History
&lt;/h2&gt;

&lt;p&gt;The project started as a crazy idea; seeing that Go and HTMX is a stack that is gaining some popularity; &lt;/p&gt;

&lt;p&gt;Go already has all the tools you need to test pure server-side rendering. But when adding a library like HTMX, the behaviour of the app is the result of a choreography between the initial DOM; the attributes on interactive elements; the HTTP endpoints reached, and the content delivered by those endpoints; both response headers and body. To verify the &lt;em&gt;behaviour from the user's point of view&lt;/em&gt;, you need a browser; or at least a test harness that behaves ... not entirely unlike a browser.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Searching for "headless browser in go" only led to results suggesting to use a &lt;em&gt;real&lt;/em&gt; browser in headless mode. This combination that has a huge overhead; discouraging the fast and efficient TDD loop. Relying on a real browser will typically &lt;em&gt;slow you down&lt;/em&gt; instead of &lt;em&gt;speed you up&lt;/em&gt;.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;So the idea was sparked; write a headless browser in pure Go as a testing tool for web applications;&lt;/p&gt;

&lt;p&gt;The first uncertainties to address was the parsing of HTML; as well as script execution. I managed to quite quickly; within 2 days to address both of these. I had a very rudimentary HTML parser; as well as I had integrated v8 into the code base&lt;sup id="fnref3"&gt;3&lt;/sup&gt; and make Go objects accessible to JavaScript code.&lt;/p&gt;

&lt;p&gt;The HTML parser was later removed, as go x/net/html already implements an HTML parser; dealing with all the quirks of HTML parsing. Parsing a well-formed document isn't a terribly difficult problem to solve. It's gracefully dealing with malformed HTML where it becomes tricky.&lt;/p&gt;

&lt;p&gt;After some time, I also managed to get &lt;em&gt;inline&lt;/em&gt; script execution to run at the right moment; i.e. the script is executes when the element is actually connected to the DOM, not after the full HTML has been parsed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with HTTP requests
&lt;/h2&gt;

&lt;p&gt;After being able to process an HTML document with inline script; the next step was to actually &lt;em&gt;download&lt;/em&gt; scripts from source. This needed to integrate an HTTP layer; such that the browser fetches content itself; rather than being fed content.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;http.Client&lt;/code&gt; also allows you to control the actual transport layer using the http.RoundTripper interface. Normally you start a server; which will listen for requests on a TCP port. In this context TCP serves as the transport layer; but is itself irrelevant for the processing of HTTP requests. Due to simplicity of the standard HTTP stack in Go; an entire HTTP server is represented by a single function, &lt;code&gt;func Handle(http.ResponseWriter, *http.Request)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The headless browser can completely bypass the overhead of the TCP stack and call this function directly using a custom &lt;code&gt;RoundTripper.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the browser can perform HTTP requests, but the browser code itself is ignorant of the fact that the HTTP layer is bypassed. And with this came the ability to download the script during DOM parsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Code
&lt;/h2&gt;

&lt;p&gt;Let's explore a simple test, as it looks now in the code base (the code uses &lt;a href="https://github.com/onsi/ginkgo" rel="noopener noreferrer"&gt;Ginkgo&lt;/a&gt; and Gomega, a somewhat overlooked combination IMHO)&lt;/p&gt;

&lt;p&gt;First, the test creates a simple HTTP handler which serves two endpoints, &lt;code&gt;/index.html&lt;/code&gt;, and &lt;code&gt;/js/script.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Should download and execute script from script tags"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// Setup a server with test content&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"GET /index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;`&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;script src="/js/script.js"&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;Hello, World!&amp;lt;/body&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"GET /js/script.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`var scriptLoaded = true`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intention here is merely to verify &lt;em&gt;that the script is executed&lt;/em&gt;. To do that, the script produces an observable side effect: It sets a value in global scope.&lt;/p&gt;

&lt;p&gt;To verify that the script was executed is just a matter of examining the global scope, which is done by executing ad-hoc JavaScript from the test itself; verifying the outcome of the expression.&lt;/p&gt;

&lt;p&gt;The code to create the browser, load the index file, and verify the observed side effect&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBrowserFromHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToNot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HaveOccurred&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;Expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunTestScript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"window.scriptLoaded"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BeTrue&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test execution is also pretty fast. The part of the test suite involving JavaScript execution currently consists of 32 tests running in 23 milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next milestone, integrate HTMX.
&lt;/h2&gt;

&lt;p&gt;As the project was initially conceived while trying to verify an HTMX application, a reasonable next goal is to support just that case. A simple HTMX application with a button and a counter, which increases when the button is pressed. This will drive the need for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An&lt;code&gt;XMLHttpRequest&lt;/code&gt; implementation needs to be in place. Work is underway for that.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;XPathEvaluator&lt;/code&gt;. I believe this can be poly-filled to begin with.&lt;/li&gt;
&lt;li&gt;Event propagation. Only &lt;code&gt;DOMContentLoaded&lt;/code&gt; and &lt;code&gt;load&lt;/code&gt; events are emitted right now. Elements need to support more events; such as &lt;code&gt;click&lt;/code&gt;; as well as methods to trigger them.

&lt;ul&gt;
&lt;li&gt;This might also require proper &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Event_bubbling" rel="noopener noreferrer"&gt;event capture and bubbling&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;Following that, more advanced user interaction; proper form handling, e.g., input handline (e.g., pressing enter in an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; field submits the form. This typically also involves some kind of URL redirection; which drives the need for a &lt;code&gt;history&lt;/code&gt; object, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration external sites
&lt;/h2&gt;

&lt;p&gt;With the ability to control the transport layer; we can provide tests with unique abilities; we can mock out external sites that the system would depend on at run-time. E.g., for a given host name, the test could provide another Go HTTP Handler simulating the behaviour.&lt;/p&gt;

&lt;p&gt;The most obvious example is the use of external identity providers. The test could simulate the behaviour of a login flow; not having to force you to create dummy accounts in an external system, have test failures because of outages in an external system, or simply being unable to automate the process at all because of 2FA or a Captcha introduced by the identity provider.&lt;/p&gt;

&lt;p&gt;Another use case is the use of API-heavy libraries, like map libraries, that incur a usage cost. Mock out the external site to not receive an extra bill for running your test suite.&lt;/p&gt;

&lt;p&gt;But given the fact that you can pass a custom &lt;code&gt;http.RoundTripper&lt;/code&gt;; it should be easy enough to create an implementation that forwards the call depending on the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usability over Compatibility
&lt;/h2&gt;

&lt;p&gt;Creating a 100% whatwg standards compliant implementation is quite an endeavour; I didn't fully comprehend the scope until I actually started reading parts of the whatwg specifications. The goal is to create a tool &lt;em&gt;helping write tests&lt;/em&gt; for web applications. Full compatibility is a long-term goal; but until the project has reached &lt;em&gt;some level of usability&lt;/em&gt;, I will not start filling out holes of missing compliance.&lt;/p&gt;

&lt;p&gt;For that reason; features that are more likely to be used in actual applications are more likely to be prioritised. A feature request pointing to an actual test giving the wrong result is likely to be prioritised.  A feature request for the implementation of a specific standard is likely to be rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spread the word
&lt;/h2&gt;

&lt;p&gt;I believe this can be a very useful tool for many developers, so if you read this, let your coworkers know it exists. This is so far a spare time project, of which I have plenty of at the moment; but that will not be the case forever.&lt;/p&gt;

&lt;p&gt;If you want to see this live, spread the word ...&lt;/p&gt;

&lt;p&gt;Perhaps you would even &lt;strong&gt;sponsor&lt;/strong&gt; this? Do you have a large company building web applications in Go? Feel free to contact me.&lt;/p&gt;

&lt;p&gt;Find the project here: &lt;a href="https://github.com/stroiman/go-dom" rel="noopener noreferrer"&gt;https://github.com/stroiman/go-dom&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Kudos if you managed to catch the homage to a popular BBC radio play. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;This is based on personal experience. Doing TDD right will speed you up due to the fast feedback cycle. The overhead of a real browser tends to make you write tests after the production code; loosing their benefit of the feedback loop a fast test suite gives you. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Groundwork had already been laid by the v8go project. However; not all features of v8 are exposed to Go code; including necessary features for embedding native objects. I was able to add those in a separate fork; which is still WIP. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>tdd</category>
      <category>go</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Go-DOM - A headless browser written in Go.</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Wed, 06 Nov 2024 13:16:26 +0000</pubDate>
      <link>https://dev.to/stroiman/go-dom-a-headless-browser-written-in-go-2hje</link>
      <guid>https://dev.to/stroiman/go-dom-a-headless-browser-written-in-go-2hje</guid>
      <description>&lt;p&gt;Having too little to do sometimes results in a crazy idea, and this time; it was to write a headless browser in Go, with full DOM implementation and JavaScript support, by embedding the v8 engine.&lt;/p&gt;

&lt;p&gt;It all started working with writing an HTMX application, and the need to test it that made me curious if there was a pure Go implementation of a headless browsser.&lt;/p&gt;

&lt;p&gt;Searching for "go headless browser" only resulted in search results talking about &lt;em&gt;automating a headless browser&lt;/em&gt;, i.e. using a real browser like Chrome of Firefox in headless mode.&lt;/p&gt;

&lt;p&gt;But nothing in pure Go.&lt;/p&gt;

&lt;p&gt;So I started building one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why A Headless Browser in Go?
&lt;/h2&gt;

&lt;p&gt;It may seem silly because writing a headless browser will never work like a real browser; and as such wouldn't really verify that your application works correctly in all the browsers you have decided to support. Neither does this allow you to get nice features such as screenshots of the application when things stop working.&lt;/p&gt;

&lt;p&gt;So why then?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Need for Speed!
&lt;/h3&gt;

&lt;p&gt;To work in an effective TDD loop, tests must be fast. Slow test execution discourages TDD, and you loose the efficiency benefits a fast feedback loop provides.&lt;/p&gt;

&lt;p&gt;Using browser automation for this type of verification has severe overheads, and such tests are typically written after the code was written; and as such, they no longer serve as a help writing the correct implementation; but are reduced to a maintenance burden after the fact; that only occasionally detect a bug before your paying customers do.&lt;/p&gt;

&lt;p&gt;The goal is to create a tool that supports a TDD process. To be usable, it needs to run in-process.&lt;/p&gt;

&lt;p&gt;It needs to be written in Go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Less Flaky Tests
&lt;/h3&gt;

&lt;p&gt;Having the DOM in-process enables writing better wrappers on top of the DOM; which can help providing a less erratic interface for your tests, like &lt;a href="https://testing-library.com/" rel="noopener noreferrer"&gt;testing-library&lt;/a&gt; does for JavaScript.&lt;/p&gt;

&lt;p&gt;Rather than depending on CSS classnames, element IDs, or DOM structure, you write your tests in a user-centric language, like this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type "&lt;a href="mailto:me@example.com"&gt;me@example.com&lt;/a&gt;" in the textbox that has the label, "Email"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or in hypothetical code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"textbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c"&gt;// The accessibility "name" of a textbox _is_ the label&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"me@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test doesn't care if the label is implemented as &lt;code&gt;&amp;lt;label for="..."&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input aria-label="Email"&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;input aria-labelledby="email-label"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This decouples verification of behaviour from UI changes; but it &lt;em&gt;does&lt;/em&gt; enforce that the text, "Email" is associated with the input field in accessible way. This couples the test to &lt;em&gt;how the user interacts with the page&lt;/em&gt;; including those &lt;em&gt;relying on screen readers&lt;/em&gt; for using your page.&lt;/p&gt;

&lt;p&gt;This achieves the most important aspect of TDD; to write tests coupled to concrete behaviour.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Although it's probably technically possible to write the same tests for an out-of-process browser; the benefit of native code is essential for the type of random access of the DOM you most likely need for these types of helpers.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example: JavaScript
&lt;/h2&gt;

&lt;p&gt;To exemplify the type of test, I will use a similar example from JavaScript; also an application using HTMX. The test verifies a general login flow from requesting a page requiring authentication.&lt;/p&gt;

&lt;p&gt;It's a bit long, as I've combined all setup and helper code in one test function here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Redirects to /local after a successful login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Setup - stub the authentication, and create a stubbed user&lt;/span&gt;
  &lt;span class="c1"&gt;// using a test helper&lt;/span&gt;
  &lt;span class="nx"&gt;sinon&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authenticate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withArgs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jd@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// matchPassword helper is used, as passwords are wrapped in a class&lt;/span&gt;
      &lt;span class="c1"&gt;// preventing accidental disclosure in logs, console out, etc.&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;matchPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3cret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AuthenticateResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`http://127.0.0.1:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/login?redirectUrl=%2Flocal`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Request private page. This _should_ generate a redirect&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;DOMWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Just a helper around jsdom&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Once HTMX is ready, it emits an `htmx:load` event. Then verify that it was &lt;/span&gt;
  &lt;span class="c1"&gt;// correctly redirected.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Use testing-library to fill out and submit the form&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabelText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jd@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s3cret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// password has no role&lt;/span&gt;
  &lt;span class="c1"&gt;// Wait for a new `htmx:load` event, while clicking the submit button&lt;/span&gt;
  &lt;span class="c1"&gt;// at the same time.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runAndWaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sign in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// After the new new page has been loaded, verify that the username&lt;/span&gt;
  &lt;span class="c1"&gt;// is displayed (i.e. the stubbed user is used), and the correct&lt;/span&gt;
  &lt;span class="c1"&gt;// URL is used.&lt;/span&gt;
  &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testingLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hi, John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In simple terms the test does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stub out the authentication function, simulating a successful response.&lt;/li&gt;
&lt;li&gt;Request a page that requires authentication&lt;/li&gt;
&lt;li&gt;Verify that the browser redirects to the login page, and the browser URL is updated. &lt;sup id="fnref2"&gt;2&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;Fill out the form with the expected values, and submit.&lt;/li&gt;
&lt;li&gt;Verify that the browser redirects to the originally requested page, and it shows information for the stubbed user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Internally the test starts an HTTP server. Because the this runs in the test process, mocking and stubbing of business logic is possible. The test use &lt;a href="https://github.com/jsdom/jsdom" rel="noopener noreferrer"&gt;jsdom&lt;/a&gt; to communicate with the HTTP server; which both parse the HTML response into a DOM, but also executes client-side script in a sandbox which has been initialised, e.g. with &lt;code&gt;window&lt;/code&gt; as the global scope.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This enables writing tests of the HTTP layer, where validating the contents of the response is not enough. In this case; that the response is processed by HTMX as intended.&lt;/p&gt;

&lt;p&gt;But apart from waiting for some HTMX events, so as to not proceed to early (or too late) the test doesn't actually care about HTMX. In fact, if I remove HTMX from the form, resorting to classical redirects, the test still pass. &lt;/p&gt;

&lt;p&gt;(If I remove the HTMX &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag completely, the test will timeout waiting for HTMX events)&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed? Check!
&lt;/h2&gt;

&lt;p&gt;While the previous test was a little slower than desired; it's reasonably fast, completing in typically 150-180ms. This is far too slow for the majority of the test suite, but it's fast enough to serve as a feedback loop while working on that particular feature.&lt;/p&gt;

&lt;p&gt;This test is not part of a normal TDD run. They are run when I work on that feature; or just before committing; ensuring nothing broke. Which is a completely normal way to deal with "slow tests".&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential Speed Improvements
&lt;/h2&gt;

&lt;p&gt;The JavaScript example uses a real HTTP server started on a random port. The server runs in the process of the test runner, which is why we can stub and mock business logic. &lt;/p&gt;

&lt;p&gt;In Go, HTTP requests are handled by an &lt;a href="https://pkg.go.dev/net/http#Handler" rel="noopener noreferrer"&gt;&lt;code&gt;http.Handler&lt;/code&gt;&lt;/a&gt;, making it very easy to consume the HTTP handling logic without actually launching an HTTP server.&lt;/p&gt;

&lt;p&gt;And this is something that the go-dom code &lt;em&gt;does&lt;/em&gt; handle right now, and currently the test suite runs in zero milliseconds, rounded to the nearest millisecond.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking and Parallel Tests?
&lt;/h2&gt;

&lt;p&gt;The ability to run parallel tests only depends on &lt;em&gt;your code&lt;/em&gt;'s ability to run in parallel. As this can consume an &lt;code&gt;http.Handler&lt;/code&gt;, each test can create its own handler; each with different dependencies replaced with test doubles as fits the individual test.&lt;/p&gt;

&lt;p&gt;This allows you to test the HTTP layer as a whole; using stubbed business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current State of the Project?
&lt;/h2&gt;

&lt;p&gt;Close to nothing is implemented; the current state is the result of about one and a half day's work. I have a basic streaming tokeniser that can consume an http response stream, which is passed to a parser that returns a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Node" rel="noopener noreferrer"&gt;&lt;code&gt;Node&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code can currently process the string &lt;code&gt;&amp;lt;html&amp;gt;&amp;lt;/html&amp;gt;&lt;/code&gt; (no spaces allowed yet) into an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLHtmlElement" rel="noopener noreferrer"&gt;&lt;code&gt;HTMLHtmlElement&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next steps are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve the parser just slightly, and get a few more element types implemented&lt;/li&gt;
&lt;li&gt;
&lt;del&gt;Embed the v8 engine, addressing the primary uncertainty&lt;/del&gt;; how are Go objects made accessible to JavaScript, and how Go code can inspect the result of mutations from JavaScript code.

&lt;ul&gt;
&lt;li&gt;I made en extremely simple POC using &lt;a href="https://github.com/rogchap/v8go" rel="noopener noreferrer"&gt;v8go&lt;/a&gt;, and what appears to be &lt;a href="https://github.com/tommie/v8go" rel="noopener noreferrer"&gt;best maintained fork&lt;/a&gt;. It lacked necessary features; which I have added in &lt;a href="https://github.com/stroiman/v8go/tree/external-support" rel="noopener noreferrer"&gt;my own fork&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future of the Project?
&lt;/h2&gt;

&lt;p&gt;This will most likely die :(&lt;/p&gt;

&lt;p&gt;I am not even working on a Go project where this would be valuable (I was working on a node.js project). It was the joy of seeing how &lt;em&gt;jsdom&lt;/em&gt; helped serve as a feedback loop for the authentication flow that sparkled a stupid idea that was fun to pursue. And as a person with ADHD, this is a typical pattern for me. I start something that is fun, work on it; until something else hits the radar and intrigues me.&lt;/p&gt;

&lt;p&gt;Unless ...&lt;/p&gt;

&lt;p&gt;Other developers think this is a good idea and want to help building this. &lt;/p&gt;

&lt;p&gt;I believe that such a tool would be extremely helpful for any Go project combining server-side rendering with client-side scripts, including HTMX-based applications.&lt;/p&gt;

&lt;p&gt;The project is found here: &lt;a href="https://github.com/stroiman/go-dom" rel="noopener noreferrer"&gt;https://github.com/stroiman/go-dom&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;The goal of TDD is &lt;strong&gt;not&lt;/strong&gt; to write unit tests. That is an extremely common; but utterly incorrect misconception. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;The important part is not that we are redirected; but that the browser history has the correct entries, providing sensible behaviour of browser back/forward functionality. The test should really have verified the contents of &lt;em&gt;history&lt;/em&gt;, or perhaps even used actively used the navigation API to go back and forth. As such; the test could be improved in regards to describing &lt;em&gt;expected behaviour from the user's point of view&lt;/em&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Wring a headless browser in JavaScript has an unfair advantage; as your simulated DOM are already valid JavaScript objects. In Go, extra work is necessary to allow client-side scripts to mutate the DOM, and let the outcome be accessible in test code. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;As reported by &lt;a href="https://github.com/onsi/ginkgo" rel="noopener noreferrer"&gt;Ginkgo&lt;/a&gt;. This doesn't include the overhead of building and launching, which has a noticeable, but very short delay. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>tdd</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My three epiphanies of TDD</title>
      <dc:creator>Peter Strøiman</dc:creator>
      <pubDate>Sun, 14 Jul 2024 14:37:00 +0000</pubDate>
      <link>https://dev.to/stroiman/my-three-epiphanies-of-tdd-1070</link>
      <guid>https://dev.to/stroiman/my-three-epiphanies-of-tdd-1070</guid>
      <description>&lt;p&gt;I once had a talented developer join my team. He was quick to understand the sometimes odd idioms used in the code base. But he wasn't that comfortable writing tests. When talking about his past experiences he said, "we often didn't have time to write tests".&lt;/p&gt;

&lt;p&gt;What strikes me as odd is, that writing the right tests first makes you faster. Of course, if you &lt;em&gt;first&lt;/em&gt; write code, and only proceed to write tests after you have already fixed all issues and established that the code works as intended; then yes, writing tests would be an investment in time with a questionable return on investments. But if you write the tests before you write the code, the tests help you implement the code faster, with fewer bugs, and more maintainable.&lt;/p&gt;

&lt;p&gt;In my career, I've had three epiphanies while practicing TDD. The first epiphany was: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing tests first makes you faster; not slower&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me write about the background story of all three events, and how they helped me become a better programmer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first epiphany
&lt;/h2&gt;

&lt;p&gt;This was the first project, where I consistently wrote tests before I wrote code. Prior to that, I had been from time to time, adding tests to existing code. But on this particular project project, there was a shared understanding that the code should be covered by tests. However, apart from me, only one other developer consistently wrote code &lt;em&gt;test first&lt;/em&gt;. A few would &lt;em&gt;sometimes&lt;/em&gt; write the test first, but most wrote the tests &lt;em&gt;after&lt;/em&gt; writing production code (often resulting in complex tests that themselves had bugs, so they didn't actually test anything)&lt;/p&gt;

&lt;p&gt;It was of course a learning process for me, I didn't get good at writing tests immediately, and today I would question if I was even practicing TDD.&lt;/p&gt;

&lt;p&gt;I wrote tests under the assumption that it was an investment. I would spend a little bit more time developing a feature. The payoff would be reduced maintenance costs. It turns out I was wrong in a way I didn't expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  One day, something marvellous happened
&lt;/h3&gt;

&lt;p&gt;One day  I was working on a feature that required modifications to ALL the layers of the application. The application was a Windows Application build using WPF following an MVVM pattern, communicating with a .NET server using WCF. This is a list of the layers I needed to modify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WPF Views&lt;/li&gt;
&lt;li&gt;View Models&lt;/li&gt;
&lt;li&gt;Internal model in the frontend&lt;/li&gt;
&lt;li&gt;Communication layer in the frontend&lt;/li&gt;
&lt;li&gt;WCF contracts&lt;/li&gt;
&lt;li&gt;Communication layer in the backend&lt;/li&gt;
&lt;li&gt;Domain model in the backend&lt;/li&gt;
&lt;li&gt;Entity framework mappings&lt;/li&gt;
&lt;li&gt;Database migrations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the exception of the WPF views, that were basically untestable, I had implemented every thing I needed in every layer by writing tests first, starting from the UI (that's what I though outside-in meant back then). I had been coding for quite a while, probably a few hours, when I was finally ready to launch the application and test everything manually before handing it over to testers for further scrutiny when something totally unexpected happened:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It worked the first time!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Did I not make any mistakes during several hours of coding? Of course I had. I had made many mistakes, but they had all been caught early by the tests. &lt;/p&gt;

&lt;p&gt;Now I started thinking, how long time would I have spent debugging, if those errors had not been caught early? At that time we didn't have a reliable mechanism for hot code reloading, so every code change would require a relaunch of the application. For a proper stateless web application that's not a big a deal; your state is in the already open browser window. But for a Windows application, I need to shut down the application, navigate through the menus to get the application into the desired state, &lt;em&gt;just to get to the point&lt;/em&gt;, where I can start debugging. Combine that with the build times, it would mean about 1-2 minutes from making a code change just to get to the point where I could reproduce the unexpected behaviour. Add to this that debugging was not trivial, as I would have no idea of knowing initially if it was the server or the client that had the bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The time saved, by not debugging was enormous!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had no doubt at all, the time necessary to find the bugs in debugging sessions would far exceed the relatively minor amount of time I had spent writing the tests. &lt;/p&gt;

&lt;p&gt;The pattern continued; from time to time, I could work on a feature that worked the first time. It wasn't always - far from it. But often when the feature didn't work initially, the problem was often trivial to find, typically a bad bindings in the WPF views, as they were virtually impossible to test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing tests first, made me faster; not slower&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These days, more than 10 years later, I almost never use a debugger. I don't even have one configured in neovim, my editor of choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second epiphany
&lt;/h2&gt;

&lt;p&gt;A few years later, I was working on a web application. This was at a time when SPAs were still not that common, and our application was delivering server-rendered HTML,  with a wee bit of JavaScript. All backend code was developed using TDD as this time all team members would write the tests first.&lt;/p&gt;

&lt;p&gt;The tooling for testing JavaScript was far from what we have today, neither was the editor support. To test code that manipulates the DOM, you basically needed to run them &lt;em&gt;in&lt;/em&gt; a browser. For that purpose we had an HTML page that would load &lt;a href="https://qunitjs.com" rel="noopener noreferrer"&gt;QUnit&lt;/a&gt;, our test code, and our production code.&lt;/p&gt;

&lt;p&gt;Previously, I had been using various Visual Studio plugins that allowed me to run tests without leaving Visual Studio. Now, when a file was saved, I needed to switch focus to the browser, and refresh the page. Fortunately, there was tool called LiveReload that could monitor the file system for changes, and trigger the browser to reload. Now, every time I saved the file, the tests would run. &lt;strong&gt;And they were FAST.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't even know what fast meant before this. I though the C# test suite was fast. Maybe we could run the test suite with thousands of tests in 10 seconds, or maybe even just a second. That is excellent for a CI server. But add compilation and startup time to that, and the fastest the feedback loop could be would still be measured in a magnitude of seconds.&lt;/p&gt;

&lt;p&gt;But the browser updated &lt;em&gt;instantaneously&lt;/em&gt;. My guess is that it was less than 200 milliseconds from saving a file to seeing the result on screen.&lt;/p&gt;

&lt;p&gt;Sometimes the HTML page would not show any test results at all. In that case, the JavaScript would have syntax errors, e.g. mismatching parenthesis or braces, an easy mistake to make as our JavaScript code relied heavily on callback functions, and there wasn't a lot of help from the editor.&lt;/p&gt;

&lt;p&gt;This experience profoundly changed the way I wrote code. I would no longer write the code to make a test pass and then save. I would save my changes every time the code was syntactically valid; typically every 5-20 seconds. Did I need to add a new UI event handler? First, add an empty event handler, save, BAM! Immediate feedback on a misplaced closing brace.&lt;/p&gt;

&lt;p&gt;In case of an error, I wouldn't bother trying to find out what I had done wrong; I would just undo my change, and try again. Quite simply, spending 30 seconds trying to identify which parenthesis or bracket that was misplaced would be silly when it takes 10 seconds to rewrite the intended code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Having instantaneous feedback while writing code profoundly changed the way I worked.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today, I will go to great lengths to setup my tooling to ensure the fastest possible feedback. Why do I still use &lt;a href="https://mochajs.org/" rel="noopener noreferrer"&gt;Mocha&lt;/a&gt; for testing JavaScript code when more modern alternatives exist? Because none of them can match the speed of mocha (despite at least one of them makes the claim to speed)&lt;/p&gt;

&lt;h2&gt;
  
  
  The third epiphany
&lt;/h2&gt;

&lt;p&gt;A few years later, I needed to connect to a RabbitMQ from a node.js project. I already had a little experience with RabbitMQ, but in Go, not node.js.&lt;/p&gt;

&lt;p&gt;But what I realised here was that there was a series of problems that needed to be solved individually.  The first problem was just establishing a connection. So I wrote something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rabbitmq communication&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;amqplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amqp://guest:guest@localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would give me the feedback I needed. First, no errors are thrown. Second, has a connection been established? I would manually verify that in the RabbitMQ management UI that lists active connections.&lt;/p&gt;

&lt;p&gt;This was the first time I wrote a "test" that wasn't a test, i.e. I didn't even try to think about what kind of verification would make sense here. But I didn't write it for verification, I needed a way to quickly execute bits of code to get feedback, and that is exactly the functionality that mocha provided making it &lt;em&gt;the perfect tool for the job&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In essence, the typical "unit test tools" basically just allow you to write a lot of small tasks, or pieces of code, that can be executed individually, and for which errors are reported. In addition to that, they also often provide an easy way to be selective about which tasks to run, allowing you to pick what is relevant for the problem you are working on, improving the feedback loop, and possibly reducing noise in the output.&lt;/p&gt;

&lt;p&gt;After I was able to successful connect, I wrote the code to create a channel over the connection, and then the code to send a message to a queue. Once again, I would manually inspect in the RabbitMQ management console that it had indeed arrived at the queue, which I had also created manually in the management UI. Then I would proceed to writing code to receive messages, and now I had the first &lt;em&gt;meaningful&lt;/em&gt; assertion to write: verify that the content of the message received is identical to the content sent.&lt;/p&gt;

&lt;p&gt;After that, I no longer depended on manual inspection. &lt;strong&gt;I was now in a state with feedback measured in milliseconds&lt;/strong&gt; and the process started to speed up dramatically; I wrote code to create queues programatically, create exchanges and bindings&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, send the message through the exchange. After each code change, within milliseconds, I knew if the message was still coming through.&lt;/p&gt;

&lt;p&gt;Eventually, I had uncovered the unknowns about how to use the library, and the "test file" contained all pieces of code necessary to setup the infrastructure. At that point in time, I would start extracting the code into a class, gradually moving the different parts of the code from the test file to production file. Still, with millisecond feedback, I would know if a message would still pass through RabbitMQ after every small change to the code.&lt;/p&gt;

&lt;p&gt;The code that started its life inline in a single test function eventually evolved into a production-mature module for communicating with RabbitMQ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Third Epiphany: TDD is NOT about writing unit tests, it is about feedback.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had long known about the idea that TDD is the "red, green, refactor" cycle. But this was the first time that refactoring was an essential part of the process, and it was the feedback that allowed refactoring.&lt;/p&gt;

&lt;p&gt;Eventually, the test changed characteristics. Rather than describing RabbitMQ interaction, they described how certain domain events should trigger other business processes, such as, "When a new user has registered, a welcome email should be sent". The test did not actually call the business logic, neither was the "register user", not the "send welcome email" code called from these tests. This had the responsibility of ensuring that a well defined event would eventually trigger a call to a use case, which had the responsibility of sending a mail. Both of these functions had been developed using different tests of tests.&lt;/p&gt;

&lt;p&gt;Eventually RabbitMQ was reduced to an implementation detail from the perspective of the test that verified the behaviour of a &lt;code&gt;Publisher&lt;/code&gt; and &lt;code&gt;Subscriber&lt;/code&gt;. In fact, RabbitMQ could potentially be replaced with another messaging technology without modifying the tests. The test suite would then be able to verify that business processes that required a temporal decoupling would still run as intended after replacing the messaging infrastructure. Other tests verified behaviour of a technical concern rather than from the business rule side. E.g. how message retry would work in the case of a failure during message processing, e.g. a broken SMTP connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authoritative sources to back my conclusions
&lt;/h2&gt;

&lt;p&gt;Before publishing this article with claims to what TDD is &lt;em&gt;really&lt;/em&gt; about, I decided to see if there are any sources where Kent Beck himself opine on the matter. I learned that &lt;a href="https://www.youtube.com/user/KentLBeck/videos" rel="noopener noreferrer"&gt;Kent Beck has a YouTube channel&lt;/a&gt;. Most of the content is really old, but I was delighted to learn that he express &lt;em&gt;exactly the same point of view&lt;/em&gt; as I do: He repeatedly states that the &lt;em&gt;goal is to get fast feedback&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a Tokyo Tyrant client library
&lt;/h3&gt;

&lt;p&gt;This video from 2010 is a small fraction of what was a longer video course on TDD by Kent Beck.  Unfortunately, the comments track suggests, the the rest is lost. Only the first 10 minutes are now available, but during those, Kent Beck specifically mentions that the primary reason for TDD is to get feedback. &lt;/p&gt;

&lt;p&gt;The actual use case is creating a Java library for connecting to &lt;a href="https://github.com/hthetiot/Tokyo-Tyrant" rel="noopener noreferrer"&gt;Tokyo Tyrant&lt;/a&gt;. Early on, he expects to end up with a &lt;code&gt;class TyrantClient&lt;/code&gt; to which the test will eventually interact. That's where he imagines that &lt;em&gt;the test will end up&lt;/em&gt;, but he doesn't even try to write that test. Rather, he writes a "test" that tries to open a TCP socket just to explore how to connect with the server. Next, he writes code, still in "the test" that transmits binary data over the socket directly; at which point in time the video ends.&lt;/p&gt;

&lt;p&gt;But the very short video is enough to show that his way of approaching the problem is almost identical to how I started using RabbitMQ from node.js; Let's just get feedback on whether or not we can establish a connection without worrying about how the "test" will look when completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use TCR to implement a rope data structure
&lt;/h3&gt;

&lt;p&gt;More recently, Kent Beck has been experimenting with a process called TCR, &lt;a href="https://www.youtube.com/watch?v=Aof0F9DvTFg" rel="noopener noreferrer"&gt;Test &amp;amp;&amp;amp; Commit || Revert&lt;/a&gt;. The idea is that after every file save, tests are automatically executed. If they pass, all changes are automatically committed to source control; otherwise, all changes are reverted, i.e. your working directory is reverted to the previous &lt;em&gt;green&lt;/em&gt; state.&lt;/p&gt;

&lt;p&gt;This process encourages &lt;em&gt;very small changes&lt;/em&gt;, and you basically end up writing code in way  similar to what I did during my second epiphany. &lt;/p&gt;

&lt;p&gt;The only important difference is that in TCR, the &lt;em&gt;undo&lt;/em&gt; is automatic, rather than manual, I would work in just as small batches as Kent Beck shows. One minor difference is that in my case, a failing test could be &lt;em&gt;the expected outcome&lt;/em&gt;, assuming it failed with the expected error. But apart from that the process was quite similar. If I didn't see the expected outcome, I'd more often undo than diagnose, potentially work in even smaller steps during the next attempt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interview with Dan North
&lt;/h3&gt;

&lt;p&gt;The third source is &lt;a href="https://www.youtube.com/watch?v=Aof0F9DvTFg" rel="noopener noreferrer"&gt;an interview with Dan North&lt;/a&gt;, the "inventor" of BDD. Originally, BDD was exactly the same as TDD was supposed to be. But in one particular project, the word "test" gave the wrong impression. Testers objected, that programmers would take their jobs (and would be bad at it), and programmers objected that they weren't testers. As the focus of the programmers was different from that of the testers, which would not be replaced, changing the term, "test" to "behaviour" made all the difference.&lt;/p&gt;

&lt;p&gt;Since then, a lot have happened in the BDD community, and today is more considered an acceptance-test-driven outside-in TDD approach, with more tools surrounding and supporting the process, such as &lt;a href="https://cucumber.io/" rel="noopener noreferrer"&gt;Cucumber&lt;/a&gt;. But just as TDD has been misinterpreted, &lt;a href="https://cucumber.io/blog/collaboration/the-worlds-most-misunderstood-collaboration-tool/" rel="noopener noreferrer"&gt;so has the intention of cucumber&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the interview, Dan states that BDD was originally intended to exactly the same as TDD was about: A process to iteratively work on the design, and he states directly "&lt;a href="https://youtu.be/_h2JF1KZQhI?t=2405" rel="noopener noreferrer"&gt;TDD ... has nothing to do with testing&lt;/a&gt;".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why and when is TDD the most efficient development method?
&lt;/h2&gt;

&lt;p&gt;I believe that TDD is the most efficient method for developing &lt;em&gt;the vast majority&lt;/em&gt; of your code base, as the fast feedback encourages you to solve the problems one at a time; potentially trying to identify the smallest possible solvable problem. You can iteratively work on the design of your code, and extract abstractions when you discover them; or remove abstractions when you realise they are no longer helpful. You can make these changes in very small steps, immediately validating your assumptions, truncating long unproductive paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  When is TDD &lt;em&gt;not&lt;/em&gt; the right choice?
&lt;/h3&gt;

&lt;p&gt;TDD isn't helpful when executing small pieces of code &lt;em&gt;doesn't provide meaningful feedback&lt;/em&gt;. The most obvious example is UI code when the focus not behaviour, but style, colours and layout. In this case, the only meaningful feedback is visually inspecting the result. Does everything &lt;em&gt;look as intended&lt;/em&gt;? Does it look good?&lt;/p&gt;

&lt;p&gt;Fast feedback is still important for efficiency, so be sure to get the fasted possible feedback. If you are writing web applications, you are probably used to hot code reload in the browser, providing near-instantaneous feedback. What about desktop applications? Automated e-mail? PDF Generation?&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;But for the vast majority of the code, a good TDD process provides the fastest possible &lt;em&gt;meaningful feedback&lt;/em&gt; in the development process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I have a good test suite?
&lt;/h2&gt;

&lt;p&gt;You can find a lot of literature about which properties a test suite should exhibit. Much of it is BS, and much describes properties that are often &lt;em&gt;observed&lt;/em&gt; in a good test suite, not the properties that make the test suite good.&lt;/p&gt;

&lt;p&gt;In this article I have tried to describe, both through my own experiences, and by referencing videos with some of the pioneers of modern TDD&lt;sup id="fnref3"&gt;3&lt;/sup&gt;, that TDD is really about the feedback, allowing you to solve one small problem at a time, and facilitating the ability to refactor to patterns as you discover them.&lt;/p&gt;

&lt;p&gt;So in essence, to identify if you have a good test suite, ask yourself the following two questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do my tests provide fast feedback when implementing new behaviour?&lt;/li&gt;
&lt;li&gt;Do my tests allow me to refactor safely?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to both questions is "yes", you have a good test suite.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Exchanges and bindings are part of the RabbitMQ infrastructure and controls how messages published are distributed to different queues owned by different consumes, respecting specific routing rules. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;In one project I needed to write code for PDF generation. I invested time into finding the tools that would automatically create and reload a PDF file in a viewer after each code change. It took some time, but the time saved by the fast feedback was worth it, allowing a fast iterative approach to writing the code. Again, the tool I used to write the code was my test framework, and once the PDF was correct, the output file was copied to an "expected" folder, used in snapshot testing to prevent a regression. I generally would advice &lt;em&gt;against&lt;/em&gt; shapshot testing, but for this purpose it was perfect. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Kent Beck only claims to have rediscovered TDD, not invented it. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

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