<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: ManoMano Tech Team</title>
    <description>The latest articles on Forem by ManoMano Tech Team (@manomano-tech-team).</description>
    <link>https://forem.com/manomano-tech-team</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%2Forganization%2Fprofile_image%2F5070%2F5978ed9e-d156-4b2e-9ce3-a6123fef4bbe.jpg</url>
      <title>Forem: ManoMano Tech Team</title>
      <link>https://forem.com/manomano-tech-team</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/manomano-tech-team"/>
    <language>en</language>
    <item>
      <title>5 Atlassian MCP Hacks to supercharge your workflow</title>
      <dc:creator>Danielo Artola</dc:creator>
      <pubDate>Thu, 16 Oct 2025 12:35:56 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/5-atlassian-mcp-hacks-to-supercharge-your-workflow-3hfj</link>
      <guid>https://forem.com/manomano-tech-team/5-atlassian-mcp-hacks-to-supercharge-your-workflow-3hfj</guid>
      <description>&lt;p&gt;I hate to be switching over and over between the IDE, the task in Jira to review details, Confluence, or other docs to ensure I have &lt;strong&gt;the&lt;/strong&gt; details, finding sometimes outdated docs, so I was curious how I could save time on that through AI. Every time we jump from writing code to updating a ticket or checking a requirement, we lose focus and momentum.&lt;/p&gt;

&lt;p&gt;After some weeks testing, configuring, &lt;strong&gt;failing&lt;/strong&gt;, and enjoying the process, I bring you here 5 situations that I found useful and saved me time. Your time is also important, so I added &lt;strong&gt;TL;DR&lt;/strong&gt; in all the sections!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The case:&lt;/strong&gt; During a recent migration of a microservice from Node.js to Kotlin, I put this to the test. Basically, the AI should help not only to do a plan for migration, &lt;strong&gt;but also&lt;/strong&gt; we needed ideas for a &lt;strong&gt;safe&lt;/strong&gt; rollout, a list of all the endpoints, business rules, external dependencies, &lt;strong&gt;and&lt;/strong&gt; adapt the plan for the team size, etc. I created a single workspace in my IDE containing the legacy Node.js project, the new Kotlin boilerplate, and another small service we were absorbing to &lt;strong&gt;provide&lt;/strong&gt; all the context I thought relevant for once I start prompting in the IDE. By giving the agent access to this full context, along with a link to our "Backend Guidelines" page in Confluence, I could kickstart the documentation process. A migration is a big topic, with several steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a plan to migrate it, and add it to somewhere easy to edit, share, review with the team, update, comment -&amp;gt; a Confluence page&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Divide the work according to the team size to achieve in a &lt;strong&gt;determined&lt;/strong&gt; time -&amp;gt; a Feature in Jira with all the tasks related&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follow our internal guidelines and avoid generic results -&amp;gt; use the current documentation in Confluence to consider them in the code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure the information in the tickets: Edit tasks descriptions, info, and adding comments with the info in the code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code fast small features or tech debt -&amp;gt; Implementing a Jira ticket&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fb1gupo4rxbvonqf4ek7t.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%2Fb1gupo4rxbvonqf4ek7t.png" alt="Steps in SDLC reinforced by AI for the microservice migration" width="642" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Documentation on Autopilot: From Code to Confluence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Use the AI to analyze projects (or specific pull requests) to generate or update technical documentation in Confluence, &lt;strong&gt;provide it with&lt;/strong&gt; the best context, ask &lt;strong&gt;it to improve&lt;/strong&gt; the prompt; this will help to have your docs sync with your code with minimal effort.&lt;/p&gt;

&lt;p&gt;Generating and &lt;em&gt;updating&lt;/em&gt; documentation is a task many of us postpone or &lt;strong&gt;sometimes defer to&lt;/strong&gt; the bottom of the backlog. With an integrated AI agent, you can delegate a significant portion of this work.&lt;/p&gt;

&lt;p&gt;Why in a document? Because it's easier to read and review than in the agent chat, and I can edit what I want to precise or correct easily.&lt;/p&gt;

&lt;p&gt;Why a conversation? Because I involved the agent in the &lt;strong&gt;decisions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why did you mention a conversation? Because I shared my idea with &lt;strong&gt;Gemini 2.5 Flash in the web&lt;/strong&gt; to help me generate the best prompt for the Cursor Agent (which primarily runs on &lt;strong&gt;Gemini 2.5 Pro&lt;/strong&gt; for coding tasks) with my ideas, and I asked &lt;strong&gt;it to ask me questions&lt;/strong&gt; to ensure the best result.&lt;/p&gt;

&lt;p&gt;After refining the prompt and editing some things, I ended with a big prompt with around 4k characters. It’s big, but needed to have a good start. Having the output in Confluence helped me to edit easily some sections, add or remove parts, add comments in some parts, and copy-paste some of the text to ask the AI if that was an assumption or something in the code (and guess what, there were some wrong assumptions, AI is not perfect, folks, yet).&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%2Fq3p1q8ekcyb95xko4c80.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%2Fq3p1q8ekcyb95xko4c80.png" alt="Steps to build a plan for migration with AI agent" width="588" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the final, structured prompt used to generate the migration plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a Senior Solutions Architect tasked with creating a comprehensive 
and actionable migration plan. 

Project: Migrate the existing [Legacy Service Name] microservice 
(Node.js/TypeScript, GraphQL, REST) to the new [Target Service Name] microservice 
(Kotlin, Ktor, GraphQL). 

Primary Goal: Complete the migration and have the new service fully operational 
in Production within a three-month timeline. 

Key Context &amp;amp; Constraints: 
Team: The project will be executed by a team of 4 backend developers. 
Technology: We must maintain GraphQL compatibility. 
The goal is to reuse the existing GraphQL schema and as much of the domain logic 
and DTOs as possible to ensure a smooth transition for frontend clients. 
Development Velocity: 
The plan should incorporate the use of AI-powered code generation tools 
to accelerate the translation of business logic from TypeScript to Kotlin 
and to generate boilerplate code. 
Standards: The entire plan, architecture, and proposed implementation must 
strictly adhere to the best practices documented in the [Your Company Name] 
Backend Guidelines found here: [Link to Backend Guidelines] 

Required Plan Structure: Please generate the plan with the following 
distinct sections: 
1. Executive Summary 
2. Gap Analysis &amp;amp; Feature Parity Strategy 
3. Development &amp;amp; AI-Assisted Strategy 
4. Phased Rollout Strategy (Zero-Risk Production Deployment) 
5. Monitoring, Logging, and Alerting Strategy ([Observability Platform]) 
6. Timeline: Sprints &amp;amp; Milestones (3 Months) 
7. Risks &amp;amp; Dependencies. 

Write the output in **Confluence**, in the space for 
[Wiki Space ID] &amp;gt; [Parent Page], called "[Project Name] Node &amp;lt;&amp;gt; KT migration".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔖 Add this to your prompts to improve results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ask me any questions you find relevant have better information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add them to a Google Doc or similar, it will be easier to answer. Then just copy all the question and answers&lt;/p&gt;

&lt;p&gt;‼️ I prefer to be specific with the destination link. Vague instructions can sometimes lead the AI to edit the wrong page. Double-check the target, I edited wrong documentation because there were two pages with the same title in different folders!&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Intelligent Task Creation, Right From the Codebase
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Define high-level constraints (time, team size) to have the AI decompose epics into manageable tasks. When you spot bad code, select it and instantly create a detailed tech debt ticket in Jira.&lt;/p&gt;

&lt;p&gt;Whether you're breaking down a massive epic or flagging unexpected tech debt, you can create perfectly detailed Jira tickets without ever leaving your code.&lt;/p&gt;

&lt;p&gt;For the migration project, I tasked the AI with planning. Here is the prompt used to decompose the epic into manageable, one-week tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Act as a senior project manager responsible for planning a microservice migration.

**Context:**
Based on the migration plan from Node.js to Kotlin for the [microservice A] to [microservice B] that we just finalized in our previous conversation, your task is to generate a detailed list of Jira tasks required to execute this plan.

**Jira Target Information:**
- Jira Feature/Epic Link to associate tasks with: [Link to Feature/Epic]

**Rules and Constraints:**
1.  **Adherence to Plan:** All tasks must directly correspond to the phases and steps we previously agreed upon.
2.  **Team &amp;amp; Timeframe:** Remember, our team consists of 4 developers and the target timeframe is 12 weeks.
3.  **Task Granularity:** No single task should exceed one week of effort for a single developer. If a step from our plan is too large, break it down into smaller, logical sub-tasks.
4.  **Dependencies:** Identify and order the tasks logically.

**Final Step (Review):**
Before you execute the creation in Jira, please present the full list of proposed tickets here in the chat for my review and final approval. This is a dry run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a bit of back-and-forth, I gave the final command to execute the creation in Jira.&lt;/p&gt;

&lt;p&gt;A more powerful feature is on-the-fly tech debt flagging. We've all been there: you're working on a feature and stumble upon a piece of convoluted "spaghetti code." Instead of letting it slide, I now select the code block and prompt the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a tech debt ticket in Jira to refactor this. 
Explain that it has high complexity and doesn't follow our guidelines. 
Reference the file @path/to/the/problematic/file.js 
in the description and propose a solution explaining the why and how.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;@&lt;/code&gt; symbol (formerly &lt;code&gt;#&lt;/code&gt; in some tools) to reference specific files is always better if you want to tag several files, try always to provide the best context for best results. It gives the AI precise, unambiguous context, leading to faster and more accurate results. You're not making it search; you're telling it exactly where to look. This focused context approach is a key feature of modern AI IDEs, allowing you to scope the AI's attention precisely.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Weaving Confluence Knowledge into Your Code
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Provide the AI with links to relevant Confluence pages (e.g., coding guidelines, business logic) to use as context when generating or refactoring code.&lt;/p&gt;

&lt;p&gt;How many times have you had to dig through Confluence to find that one specific business rule or coding standard? I don’t remember 100% of things, and I hate to be searching some specific info in Confluence, &lt;strong&gt;switching over and over&lt;/strong&gt; screens. Now, you can bring that knowledge directly to the agent that writes your code.&lt;/p&gt;

&lt;p&gt;When I need the AI to generate code that must adhere to complex business logic documented in Confluence (or follow several internal guidelines), I start a conversation by providing the link. I first verify it has understood the content with a prompt like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summarize the key business rules from this document regarding user authentication.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I confirm the agent has "ingested" the knowledge correctly, I can proceed with my code generation request, confident that it will respect the documented constraints.&lt;/p&gt;

&lt;p&gt;🔖 If you don’t trust &lt;strong&gt;the&lt;/strong&gt; AI answers 100% like me, I recommend you to ask something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain to me how you took the info provided and how you adapted it 
and considered it in the code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is currently the best way to provide external knowledge through Atlassian MCP. While Atlassian Intelligence is moving towards a future where the AI can automatically index and understand your entire Confluence space, the "link and verify" method is the best practice at the moment of writing this.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The IDE as Your Jira Cockpit
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Use natural language commands in your IDE to add comments to tickets, change their status, or clarify descriptions, drastically reducing the need to switch to the Jira web UI.&lt;/p&gt;

&lt;p&gt;Small, repetitive Jira actions add up, pulling you out of your coding mindset. You can execute most of these tasks with a single command from your editor.&lt;/p&gt;

&lt;p&gt;The actions that save me the most time are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adding Contextual Comments:&lt;/strong&gt; Highlighting a discrepancy in the code and telling the agent, "Add a comment to ticket XYZ explaining that the current implementation doesn't fit our business rules and requires extra development not considered in the task."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Changing Status:&lt;/strong&gt; Simple commands like &lt;code&gt;Move this ticket to 'In Progress' and give me a summary of the app&lt;/code&gt; or &lt;code&gt;Move this ticket to 'Code Review' and push the code&lt;/code&gt; (I don’t waste tokens just to move a ticket, I do it with a relevant action).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clarifying Ticket Descriptions:&lt;/strong&gt; Sometimes a ticket's description is too vague or you forgot to mention something, or you have to correct a wrong assumption (yes, all of that sometimes happens). After discovering the necessary technical details, I'll ask the agent to update it. This is a great practice for enriching the ticket with implementation details.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‼️ However, be mindful of when editing a description is appropriate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Good Practice (Clarification):&lt;/strong&gt; Adding technical notes, like "The new field must be encrypted in the database using AES-256." This is the precise command used for ticket refinement:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Edit the description removing assumptions and adding only things in the code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Bad Practice (Scope Creep):&lt;/strong&gt; Changing the core requirement, like turning "Add a 'First Name' field" into "Add 'First Name' and 'Date of Birth' fields and a new validation flow." The latter should be a new ticket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jira ticket created with generic description, generated in a bundle with several tasks:&lt;br&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%2Fhtz8jbj0cj7zrv4t1ruc.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%2Fhtz8jbj0cj7zrv4t1ruc.png" alt="Jira ticket created with generic description, generated in a bundle with several tasks" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A different task, but this with detailed description after asking to improve it with code, links and relevant info:&lt;br&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%2Fmfy4my4kazl146at6xar.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%2Fmfy4my4kazl146at6xar.png" alt="A different task, but this with detailed description after asking to improve it with code, links and relevant info" width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  5. From Jira Ticket to Code, and Beyond with Automation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; For simple and well-scoped Jira tickets, you can ask the AI to generate the code just by providing the ticket link. This opens the door to automating entire parts of your development lifecycle.&lt;/p&gt;

&lt;p&gt;For well-defined, simple tasks, you can go from a Jira ticket link to functional code in minutes. This is where we can start thinking about true workflow automation.&lt;/p&gt;

&lt;p&gt;This works best for tasks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Adding a new optional field to an API endpoint.&lt;/li&gt;
&lt;li&gt;  Changing UI label text for clarity or rewording.
&lt;/li&gt;
&lt;li&gt;  Adding missing translations.&lt;/li&gt;
&lt;li&gt;  Generating unit tests for a function or improving test coverage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔖 The prompt is as simple as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement the changes described in this Jira ticket: [link].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important &lt;strong&gt;thing&lt;/strong&gt; is to ensure a good description in the ticket.&lt;/p&gt;

&lt;p&gt;This leads to the next logical step: automation. For example, here is a powerful multi-action prompt that links code analysis, documentation, and task creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document the endpoints of the [Legacy Service Name] Node.js project for migration. 

Follow these steps to generate a **Confluence** page and a **Jira** task. 

1. **Confluence** Page Documentation: 
Generate a new page at the URL: [link here] 
For each endpoint in the [Legacy Service Name] project, create a new section with a descriptive title. Within each section, provide the following details: Route, Method, Parameters, Response Example, OpenAPI Link. 
At the end of the page, create a sortable table with the following columns: Status, Method, Endpoint, and Link to openapi. Use the sortable table macro or a similar feature if available in **Confluence** to enable sorting. 

2. **Jira** Task Creation: 
Create a new **Jira** task with the following specifications: 
Parent Epic: [link here] 
Title: Endpoints to migrate from [Legacy Service Name] 
Description: Include a TLDR section with a brief summary of the task. After the TLDR, include the same table you generated for the **Confluence** page.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the next frontier: a truly automated, intelligent development environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Setting Up Your AI Cockpit: MCP Atlassian Integration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Install the specific Atlassian Marketplace Connect Program (MCP) agent/plugin in your preferred editor (Cursor, IntelliJ, VS Code) and authenticate with your Atlassian workspace.&lt;/p&gt;

&lt;p&gt;As always, there are requirements, some steps to follow depending on the IDE you use, etc. Here you'll see the instructions for all the IDEs. Let's see the example for Cursor&lt;/p&gt;

&lt;h4&gt;
  
  
  A. Cursor
&lt;/h4&gt;

&lt;p&gt;Since Cursor is built with AI capabilities from the start, the integration is often seamless. The Cursor agent uses &lt;strong&gt;Gemini 2.5 Pro&lt;/strong&gt; as its underlying model for the deep reasoning and code generation tasks described. (you also can add it easily from &lt;a href="https://cursor.com/docs/context/mcp/directory" rel="noopener noreferrer"&gt;the cursor mcp list&lt;/a&gt;).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Open Settings:&lt;/strong&gt; Navigate to &lt;code&gt;Settings&lt;/code&gt; (usually &lt;code&gt;Cmd+,&lt;/code&gt; or &lt;code&gt;Ctrl+,&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Find Integrations:&lt;/strong&gt; Look for "MCP".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connect Atlassian:&lt;/strong&gt; Select the Atlassian integration option (Jira/Confluence) and follow the prompts to sign in with your Atlassian credentials. This connects the internal &lt;code&gt;atlassian-mcp-server&lt;/code&gt; to your agent, granting it read access to your permitted documentation and ticketing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes, if you have troubles installing the mcp in your ide, you can ask to your favourite model in your IDE copy pasting the errors or explaining the problems! Or, of course, you can comment in this post and we'll try to help&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Integrating Atlassian's AI tools into your IDE isn't about replacing the developer. It's about augmenting our abilities by removing friction, having the developer focus &lt;strong&gt;on&lt;/strong&gt; development, and avoid the &lt;strong&gt;satellite&lt;/strong&gt; tasks that are repetitive and from what you learn little to nothing. By handling the overhead of documentation, task management, and context gathering, it allows us to dedicate our most valuable resource—our focused brainpower—to what we do best: building bold and &lt;strong&gt;ingenious&lt;/strong&gt; software.&lt;/p&gt;

&lt;p&gt;What are your favorite AI-driven workflows? Share them in the comments below!&lt;/p&gt;

&lt;p&gt;AI created, &lt;strong&gt;Human-enhanced&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Daniel Artola.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>productivity</category>
      <category>ai</category>
      <category>cursor</category>
    </item>
    <item>
      <title>Wiremock + testcontainers + Algolia + Go = ❤️</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Fri, 20 Jun 2025 08:16:02 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/wiremock-testcontainers-algolia-go--3hn7</link>
      <guid>https://forem.com/manomano-tech-team/wiremock-testcontainers-algolia-go--3hn7</guid>
      <description>&lt;p&gt;When dealing with a SaaS like &lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;, testing can be a hassle. Ideally, you should not "mock what you do not own". In other words, you should not mock libraries such as the Algolia SDK, not just because it might evolve in unforeseen ways, but also because writing unit tests for a piece of code where the logic is dictated by something external to the code is not a good idea: you would not be testing the part that has the most complexity.&lt;/p&gt;

&lt;p&gt;To take a concrete example, let's imagine you want to index documents in Algolia. There is an end goal behind that, and the end goal is that it is possible to search for these documents.&lt;/p&gt;

&lt;p&gt;Ideally, you would have a Docker container running Algolia locally that would be super fast at indexing and use the same code your production Algolia app uses, but sadly that does not exist, and I'm not hopeful it ever will.&lt;/p&gt;

&lt;p&gt;In a legacy service I worked on, we have a test Algolia app that we use for integration tests. It worked great, but in the past years, Algolia introduced a new cloud-based architecture, and with this architecture, an indexing task can take a lot more time to be "published". As a result, using a test application on the cloud-based architecture is not an option anymore, as it slows the test suite down to a crawl. 🐌&lt;/p&gt;

&lt;p&gt;On a new project, I decided to re-evaluate my options, and remembered a tool that seems to be the next best thing for the job: &lt;a href="https://wiremock.org" rel="noopener noreferrer"&gt;Wiremock&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I will guide you through the process of setting Wiremock and testcontainers to test Algolia's own &lt;a href="https://www.algolia.com/doc/libraries/go/v4/" rel="noopener noreferrer"&gt;quickstart guide for Golang&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiremock: a VCR for HTTP
&lt;/h2&gt;

&lt;p&gt;Wiremock is a testing tool that comes with a so-called &lt;a href="https://wiremock.org/docs/record-playback" rel="noopener noreferrer"&gt;"record and&lt;br&gt;
playback"&lt;/a&gt; feature.&lt;/p&gt;

&lt;p&gt;It means you can do this once in your local environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐          ┌────────────────────────────┐       ┌─────────┐
│            ├─────────►│                            ├──────►│         │
│Your service│          │ Wiremock in recording mode │       │ Algolia │
│            │◄─────────┤                            │◄──────┤         │
└────────────┘          └────────────────────────────┘       └─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In recording mode, you give Wiremock a URL to record, and it will store files representing the requests you made, and the corresponding responses. With Algolia, it can be quite long, especially if you &lt;a href="https://www.algolia.com/doc/api-reference/api-methods/wait-task" rel="noopener noreferrer"&gt;wait for operations&lt;/a&gt;.&lt;br&gt;
What happens in practice is that the SDK will use a polling mechanism to check if your task is published. This will result in a lot of similarly looking files.&lt;br&gt;
This is not very interesting to reproduce in your test, so I recommend simply deleting files representing a negative response to the question: "are the changes published yet?". Those typically contain a JSON field called &lt;code&gt;status&lt;/code&gt; set to &lt;code&gt;notPublished&lt;/code&gt; in their body, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notPublished"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pendingTask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the file is published, this becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"published"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pendingTask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The files have names that are a bit ugly, so I usually rename them for clarity.&lt;br&gt;
For example, you might rename &lt;code&gt;1_indexes_test-index_task_226434943725-6e8689fa-9bbb-43fb-9d24-6824c02fc7d5.json&lt;/code&gt;&lt;br&gt;
to &lt;code&gt;index_test_task_published.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once your recording is done, you can run your tests like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐          ┌───────────────────────────┐
│            ├─────────►│                           │
│Your service│          │ Wiremock in playback mode │
│            │◄─────────┤                           │
└────────────┘          └───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In playback mode, Wiremock will respond to your request with the mappings it has stored previously, and pretend to be Algolia. 🥸&lt;/p&gt;

&lt;p&gt;While this does not shield you against breaking changes in the Algolia HTTP API, it does come with a few advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It shields you against breaking changes or bugs in the Algolia SDK.&lt;/li&gt;
&lt;li&gt;You no longer have to mock the SDK, which is a bad practice and a pain to do. A consequence of that is that your tests become easier to understand, and more expressive, and that they check things at a higher level rather than focusing on implementation details.&lt;/li&gt;
&lt;li&gt;It still means that at least once, you do run the tests against the real thing, so if there is some issue that can only be detected at runtime, you will know about it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wiremock is a java application, but that shouldn't matter too much, especially given there is an &lt;a href="https://hub.docker.com/r/wiremock/wiremock" rel="noopener noreferrer"&gt;official Docker image&lt;/a&gt; you can use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testcontainers: Docker for your tests
&lt;/h2&gt;

&lt;p&gt;At ManoMano, we use Gitlab CI. While it is possible to define &lt;a href="https://docs.gitlab.com/ci/services/" rel="noopener noreferrer"&gt;a Gitlab CI service&lt;/a&gt; with the aforementioned Docker image, that's not a great solution because Gitlab services do not expose the full power of Docker. For instance, mounting a volume is not possible, probably not without heavy involvement of privileged users.&lt;/p&gt;

&lt;p&gt;A great alternative is &lt;a href="https://testcontainers.com" rel="noopener noreferrer"&gt;testcontainers&lt;/a&gt; + &lt;a href="https://testcontainers.com/cloud/" rel="noopener noreferrer"&gt;testcontainers Cloud&lt;/a&gt;. Testcontainers is a library available in many languages that allows you to start and stop Docker containers during your tests, making it possible to get good isolation between tests.&lt;br&gt;
Testcontainers Cloud is a service that allows you to run said containers on a remote infrastructure, as opposed to running them on your own infrastructure, which, if you want to use Kubernetes runners for Gitlab, implies using Docker in Docker, which is not great from the security standpoint.&lt;br&gt;
Locally, you would still use a local docker container, but in the CI,&lt;br&gt;
tescontainers will send requests to testcontainers cloud, to start and stop containers. Enough unpaid endorsement, let's get to the code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo time
&lt;/h2&gt;

&lt;p&gt;Let us follow &lt;a href="https://www.algolia.com/doc/libraries/go/v4/" rel="noopener noreferrer"&gt;Algolia's quickstart guide for Golang&lt;/a&gt; and see how easy it is to&lt;br&gt;
test.&lt;/p&gt;

&lt;p&gt;If you want to follow along, install &lt;a href="https://mise.jdx.dev/getting-started.html" rel="noopener noreferrer"&gt;mise-en-place&lt;/a&gt; and let's go!&lt;/p&gt;

&lt;p&gt;For the sake of brevity, I will not systematically show the entirety of a file I edit in all snippets, however I have tried to create one commit per step in &lt;a href="https://github.com/greg0ire/wiremock-blogpost/commits" rel="noopener noreferrer"&gt;this Github repository&lt;/a&gt;, in case you would like to play with the code or simply read it in your own editor.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mise use go@1.24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Creating a new project
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go mod init algolia-wiremock-testcontainers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Installing the Algolia SDK
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/algolia/algoliasearch-client-go/v4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Setting up the environment
&lt;/h3&gt;

&lt;p&gt;At this point, you will need to set up a test Algolia application. Once you are done, you should have an application ID and an API key.&lt;/p&gt;

&lt;p&gt;Let us use an unversioned env file to store our credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# mise.toml&lt;/span&gt;
&lt;span class="nn"&gt;[env]&lt;/span&gt;
&lt;span class="py"&gt;_.file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".env"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;changeme
&lt;span class="nv"&gt;ALGOLIA_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;changeme
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need to replace &lt;code&gt;ALGOLIA_APP_ID&lt;/code&gt; and &lt;code&gt;ALGOLIA_API_KEY&lt;/code&gt; with values from &lt;a href="https://dashboard.algolia.com/account/api-keys" rel="noopener noreferrer"&gt;your account&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitignore&lt;/span&gt;
/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing the code to be tested
&lt;/h3&gt;

&lt;p&gt;Let us take the code from Algolia's quickstart guide and split it into two files:&lt;/p&gt;

&lt;p&gt;First, we have the code under test where the only changes are getting the environment variables from the actual environment, and renaming packages and functions.&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;// indexer.go&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;indexer&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/search"&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;indexRecord&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Get Algolia credentials from environment variables&lt;/span&gt;
    &lt;span class="n"&gt;appID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&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="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"objectID"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"test record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Create a new Algolia client&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;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&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="c"&gt;// Add record to an index&lt;/span&gt;
    &lt;span class="n"&gt;saveResp&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSaveObjectRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="c"&gt;// Wait until indexing is done&lt;/span&gt;
    &lt;span class="n"&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitForTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saveResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TaskID&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it work, you will need to install the Algolia SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/algolia/algoliasearch-client-go/v4
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That call to &lt;code&gt;WaitForTask&lt;/code&gt; is what is going to take the most time, and a good reason not to use a real Algolia instance in your test suite. That's what we are going to try first though.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing the test with a real Algolia instance
&lt;/h3&gt;

&lt;p&gt;Let's start simple and write a first version of the test that talks directly to Algolia:&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;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;indexer&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/debug"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/search"&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;TestIndexRecord&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;// helps with seeing the progress, since this is super long&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;appID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&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;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create client: %v"&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="c"&gt;// Create index indirectly by setting settings&lt;/span&gt;
    &lt;span class="n"&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSetSettingsRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"test-index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptyIndexSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSearchableAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to set settings: %v"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&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;// Ensure the test index is deleted after the test completes&lt;/span&gt;
        &lt;span class="n"&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiDeleteIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to delete index: %v"&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="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Call the function under test&lt;/span&gt;
    &lt;span class="c"&gt;// this should index a record in Algolia&lt;/span&gt;
    &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Verify the record was indexed by searching for it&lt;/span&gt;
    &lt;span class="c"&gt;// This search should return record with "test" in their contents&lt;/span&gt;
    &lt;span class="c"&gt;// The quickstart guide currently uses a more complex version of this&lt;/span&gt;
    &lt;span class="n"&gt;searchResp&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchSingleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSearchSingleIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;WithSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchParamsObjectAsSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptySearchParamsObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                        &lt;span class="n"&gt;SetQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&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="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="c"&gt;// Assert that there are hits&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No hits found"&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;aaaaand that doesn't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;panic: The maximum number of retries exceeded. (50/50) [recovered]
        panic: The maximum number of retries exceeded. (50/50)

goroutine 7 [running]:
testing.tRunner.func1.2({0x800600, 0xc00028d640})
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1734 +0x21c
testing.tRunner.func1()
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1737 +0x35e
panic({0x800600?, 0xc00028d640?})
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/runtime/panic.go:792 +0x132
algolia-wiremock-testcontainers.indexRecord()
        /home/gregoire/Documents/blogging/wiremock/indexer.go:39 +0x166
algolia-wiremock-testcontainers.TestIndexRecord(0xc000198540)
        /home/gregoire/Documents/blogging/wiremock/indexer_test.go:40 +0x20a
testing.tRunner(0xc000198540, 0x8a6c10)
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1792 +0xf4
created by testing.(*T).Run in goroutine 1
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1851 +0x413
FAIL    algolia-wiremock-testcontainers 185.745s
FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have many applications on this instance, some of which are very busy, let us patch that real quick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Wait until indexing is done
_, err = client.WaitForTask(
    indexName,
    saveResp.TaskID,
    search.WithMaxRetries(100),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exactly the type of thing that unit tests will not catch.&lt;/p&gt;

&lt;p&gt;After that, the test passes (but it takes between several seconds or several minutes to run depending on how busy the instance on which the application is running is). Great! Now, let's add a proxy in the middle, and record all this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Wiremock in record mode 📼
&lt;/h3&gt;

&lt;p&gt;We are using Docker, so if we want to obtain the so-called "mapping files" Wiremock will create, we need to mount a volume on our Docker container, and mount it in the right location.&lt;/p&gt;

&lt;p&gt;Let us add 2 new dependencies to our project:&lt;/p&gt;

&lt;p&gt;We could interact with Wiremock by calling the REST API with the &lt;code&gt;net/http&lt;/code&gt; package, but as it turns out, there is a dedicated SDK for that, and it supports recording since &lt;a href="https://github.com/wiremock/go-wiremock/pull/33" rel="noopener noreferrer"&gt;this pull request I sent&lt;/a&gt;. Let's install it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/wiremock/go-wiremock@v1.13.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will need a way to start and stop the Wiremock container, and for that&lt;br&gt;
as well, there is a library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/wiremock/wiremock-testcontainers-go@v1.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us start the container, with a volume mounting &lt;code&gt;testdata&lt;/code&gt; in the current directory on &lt;code&gt;/home/wiremock/mappings&lt;/code&gt; in the container. This is where Wiremock will create json files.&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;// indexer_test.go&lt;/span&gt;

&lt;span class="c"&gt;// for some reason wiremock doesn't like the testing context&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;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

&lt;span class="n"&gt;absolutePath&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getwd&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get current working directory: %v"&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="c"&gt;// Start the Wiremock container,  using the testcontainers library&lt;/span&gt;
&lt;span class="n"&gt;container&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;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunContainerAndStopOnCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&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;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHostConfigModifier&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;hostConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostConfig&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;hostConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Binds&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata:/home/wiremock/mappings"&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;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wiremock/wiremock:3.12.1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create wiremock container: %v"&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="c"&gt;// The endpoint changes every time, so we need to obtain it at runtime&lt;/span&gt;
&lt;span class="n"&gt;host&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;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get wiremock container endpoint: %v"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to change how we instantiate the Algolia client, so that it calls Wiremock instead of Algolia:&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;algoliaClient&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;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClientWithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchConfiguration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="o"&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;Configuration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;AppID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Hosts&lt;/span&gt;&lt;span class="o"&gt;:&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;StatefulHost&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;NewStatefulHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&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;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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create client: %v"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I have renamed the client to &lt;code&gt;algoliaClient&lt;/code&gt; to avoid confusion with the Algolia client and the Wiremock client.&lt;/p&gt;

&lt;p&gt;Let us also refactor our &lt;code&gt;indexRecord()&lt;/code&gt; function to take the client as an argument:&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;// indexer.go&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&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="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"objectID"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"test record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;saveResp&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSaveObjectRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitForTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;saveResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TaskID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMaxRetries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's start the recording, and for that we need a client to call Wiremock's administration API:&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;// indexer_test.go&lt;/span&gt;

&lt;span class="n"&gt;wiremockClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;host&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;Cleanup&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to reset wiremock: %v"&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="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// Call Wiremock's administration API to start recording&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;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"https://%s-dsn.algolia.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to start recording: %v"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&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;// Call Wiremock's administration API&lt;/span&gt;
        &lt;span class="c"&gt;// to stop recording and dump the mappings&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;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopRecording&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to stop recording: %v"&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="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// code that interacts with Algolia must be after&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's run our tests again, check our &lt;code&gt;testdata&lt;/code&gt; directory, and see what's new.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; testdata
&lt;span class="go"&gt;1_indexes_test-index-4c8f560d-010a-4db9-916c-f5c112481fc8.json
1_indexes_test-index-e766b4bf-6087-499b-975f-722c451f1d4a.json
1_indexes_test-index_query-e08439bf-fd60-4fac-8181-11b72f7a7a1c.json
1_indexes_test-index_settings-9de043d2-5803-40a0-b19b-49d0d2a17dde.json
1_indexes_test-index_task_228603771246-0004a7a6-3759-452c-8d0d-ea2a2f948ea1.json
1_indexes_test-index_task_228603771246-012f5d71-2645-496c-ba68-b85dcafbce51.json
1_indexes_test-index_task_228603771246-050f8624-f6f5-4815-96ed-03f319cdbda0.json
1_indexes_test-index_task_228603771246-05510684-a31f-41a6-96aa-d722a7527e87.json
1_indexes_test-index_task_228603771246-07651b21-23b0-45b4-9e13-386775c37432.json
1_indexes_test-index_task_228603771246-0bfcad3d-4d19-403f-9197-2fe6214beeb2.json
1_indexes_test-index_task_228603771246-0d450b99-1af0-4761-8d92-d7b92a68c702.json
1_indexes_test-index_task_228603771246-0dd66ddc-d7e2-4f70-8a3f-f5934ade7ac1.json
1_indexes_test-index_task_228603771246-1490e160-63e7-466d-9405-9437efd31c68.json
1_indexes_test-index_task_228603771246-1d25eb69-2a0f-448d-ae60-7075c380837d.json
1_indexes_test-index_task_228603771246-1f675d54-54bc-41cc-a3b4-f3dcc153cb0b.json
1_indexes_test-index_task_228603771246-1f829991-f442-45b6-9463-6ad064eb7576.json
1_indexes_test-index_task_228603771246-22837672-68b3-4684-a1f0-5bf19a8a3e8d.json
1_indexes_test-index_task_228603771246-25162e07-d196-4527-b476-173b7d62acf7.json
1_indexes_test-index_task_228603771246-26087497-e8ee-429a-a98e-a0100219c112.json
1_indexes_test-index_task_228603771246-264ce294-058a-482e-8d33-8be46740c7e9.json
1_indexes_test-index_task_228603771246-272cd045-d145-4b26-86b7-accad674a2db.json
1_indexes_test-index_task_228603771246-2ac3189e-9737-46df-a1b5-269e63a4d36a.json
1_indexes_test-index_task_228603771246-30a023df-b21b-43e7-b1a0-3f3c6ceec6d4.json
1_indexes_test-index_task_228603771246-315a9105-a169-4228-9d6a-7f20e9db3e4a.json
1_indexes_test-index_task_228603771246-3358c4a2-9c5d-4c7f-982f-1ca49c413b08.json
1_indexes_test-index_task_228603771246-34ef8259-1204-4026-9c1d-d43731a8d489.json
1_indexes_test-index_task_228603771246-37be5ee5-2f56-43a9-afe3-866859945d72.json
1_indexes_test-index_task_228603771246-3ba76a13-6052-4b9d-a7f2-61d3d364714d.json
1_indexes_test-index_task_228603771246-3be6df0a-3bcc-4104-81e0-28462875ed86.json
1_indexes_test-index_task_228603771246-3da6c29e-5603-4262-bfa8-37a06b021297.json
1_indexes_test-index_task_228603771246-3e321244-de3c-420e-978e-60b995839ca2.json
1_indexes_test-index_task_228603771246-41ed4f34-d05b-4e30-a9da-fd1b84d85aaa.json
1_indexes_test-index_task_228603771246-420e2d2c-2aa0-4afd-9cdd-5ac6ecc7bd20.json
1_indexes_test-index_task_228603771246-4220ae1a-447c-419f-86f9-6ee5bc40a121.json
1_indexes_test-index_task_228603771246-42b9612d-005a-4e5e-bb8c-e0e431790404.json
1_indexes_test-index_task_228603771246-450ab804-2980-478f-b1f2-c9cef5ba021c.json
1_indexes_test-index_task_228603771246-472b813d-f05a-4bca-bc14-c8f0b085388c.json
1_indexes_test-index_task_228603771246-4802a463-b49f-4ddf-83cd-49af7615c66a.json
1_indexes_test-index_task_228603771246-49ed408e-d531-4bc5-a711-2f1e4acc4ae2.json
1_indexes_test-index_task_228603771246-4a9c9104-f042-4f9a-a56b-dc4a9976bde7.json
1_indexes_test-index_task_228603771246-4ae60200-dbed-4c1b-878f-6dcf53cf4954.json
1_indexes_test-index_task_228603771246-4df81e83-7ce3-4ed1-9102-48c987a675de.json
1_indexes_test-index_task_228603771246-4e11ef64-ade6-40ca-b5c0-8a35684f73cd.json
1_indexes_test-index_task_228603771246-5383a9a9-7023-41f9-8092-09bbde387e7d.json
1_indexes_test-index_task_228603771246-55f8777f-8ff1-4fdb-ae35-86bd8d1641f6.json
1_indexes_test-index_task_228603771246-581cce11-3bbe-4492-b9b2-4bb4db47e50f.json
1_indexes_test-index_task_228603771246-58a94473-05f6-460d-b595-87d67a6389cc.json
1_indexes_test-index_task_228603771246-5aadb748-f27e-40b5-8166-33e61de59888.json
1_indexes_test-index_task_228603771246-5eabca6c-637d-4a3d-a883-9a0ac8d40449.json
1_indexes_test-index_task_228603771246-628fb430-b307-4257-8a23-becfcd8c1649.json
1_indexes_test-index_task_228603771246-660b5321-6c43-41e1-83d8-02f936a09bc9.json
1_indexes_test-index_task_228603771246-6b15a9eb-886f-43ff-af1e-2b82a8dacdf2.json
1_indexes_test-index_task_228603771246-6c610574-d0cf-4272-9260-cd5c42954b1c.json
1_indexes_test-index_task_228603771246-6e0d4b06-c3dc-47b8-858b-1d66ed65f708.json
1_indexes_test-index_task_228603771246-70303855-6eb1-465e-949b-40c85e6b5d2e.json
1_indexes_test-index_task_228603771246-712fc20e-8877-43e0-998b-3408c85a1645.json
1_indexes_test-index_task_228603771246-71ede81b-df18-4f39-97ea-757967a84599.json
1_indexes_test-index_task_228603771246-72c592d9-6d61-4a22-bf3e-3d2559b319a8.json
1_indexes_test-index_task_228603771246-72de2fd6-675c-42e6-85d0-21b3947b3fc1.json
1_indexes_test-index_task_228603771246-74ca6363-881f-49b8-8dac-efd6f5c8d449.json
1_indexes_test-index_task_228603771246-75f4709b-ef34-4eed-9d2e-002a3237a880.json
1_indexes_test-index_task_228603771246-773d2b22-6e45-4b0b-943b-b8e261c38d9b.json
1_indexes_test-index_task_228603771246-7accc591-6809-4483-9b39-578557dcabd3.json
1_indexes_test-index_task_228603771246-7bd2b559-9f16-4c57-92bb-0020763419b1.json
1_indexes_test-index_task_228603771246-837f6509-a8b8-4f0a-98fd-4f3b82349acc.json
1_indexes_test-index_task_228603771246-8875e9eb-07f3-4565-9e45-9db6b8c4a662.json
1_indexes_test-index_task_228603771246-8b464ab1-bef7-4975-972a-c472bfca9a90.json
1_indexes_test-index_task_228603771246-8c42ea24-0b42-4ed3-91a3-7cbce16828e0.json
1_indexes_test-index_task_228603771246-932fe93f-2121-4b1a-bedb-334a4518b986.json
1_indexes_test-index_task_228603771246-9697ecdf-4079-4af0-9df5-2d3f44f2b26c.json
1_indexes_test-index_task_228603771246-9bbbf58d-a4ea-47ba-af43-d7a455cc333a.json
1_indexes_test-index_task_228603771246-9d4fbd76-3f4c-4605-aa88-49d105bf718c.json
1_indexes_test-index_task_228603771246-9f126ad6-8a7a-4a37-80ef-7528c62f939d.json
1_indexes_test-index_task_228603771246-9f247f3d-9ba1-447f-a9d1-f61919a40d65.json
1_indexes_test-index_task_228603771246-a01ab516-b606-47b2-b4db-c1188fd9527a.json
1_indexes_test-index_task_228603771246-a057d6b6-00fe-40ea-aee8-bc0873bfe66d.json
1_indexes_test-index_task_228603771246-a460d1b7-6d5f-46b4-9196-a0a49c6c9e10.json
1_indexes_test-index_task_228603771246-a7c15221-1a8f-48b2-9c20-c90bdb6f0074.json
1_indexes_test-index_task_228603771246-a90bdd7d-7be0-428b-973f-1ba429c10f99.json
1_indexes_test-index_task_228603771246-b06c842b-102e-4ca9-99b2-38d838166480.json
1_indexes_test-index_task_228603771246-b2fbbe73-e3cb-49ee-a008-05a3eb66c93d.json
1_indexes_test-index_task_228603771246-b4a8ce95-12f3-4786-9dca-e989f86873ef.json
1_indexes_test-index_task_228603771246-baf4c6a3-ac78-44df-9e5d-8798e9dcf1ff.json
1_indexes_test-index_task_228603771246-bee44a4d-b090-4ebf-b2c7-3b85a0c92000.json
1_indexes_test-index_task_228603771246-c367963c-3aca-41dd-a570-c3135702c841.json
1_indexes_test-index_task_228603771246-c4f8d672-f2ed-4b37-ad51-04ceacf8f3e0.json
1_indexes_test-index_task_228603771246-cbf506c8-70c9-406a-afaa-7db6b7212345.json
1_indexes_test-index_task_228603771246-cc952870-85f4-4057-bb42-24940e3a9050.json
1_indexes_test-index_task_228603771246-d46e09e2-0634-40c6-b121-9c488000a698.json
1_indexes_test-index_task_228603771246-d85de2d1-2dbd-472f-b7d7-e288f833867c.json
1_indexes_test-index_task_228603771246-d998fa67-ef81-407d-9ced-4549b63be22e.json
1_indexes_test-index_task_228603771246-dbebd7ae-ef9a-41b4-9f67-8e8cb871522f.json
1_indexes_test-index_task_228603771246-dec87bcc-2c17-4f38-97dc-f00d3346a00b.json
1_indexes_test-index_task_228603771246-e1228862-9f69-4224-8f46-135b307edd5a.json
1_indexes_test-index_task_228603771246-e4ea7ed5-6f42-4eb7-a54c-f25609c54f3a.json
1_indexes_test-index_task_228603771246-e4fdbcf5-1941-44bd-87bb-8f7c9b0c4718.json
1_indexes_test-index_task_228603771246-ea16fd95-c2ca-474f-a4d2-e556c5bd158c.json
1_indexes_test-index_task_228603771246-f657c74c-0e47-4a6b-887d-e003ba7118c6.json
1_indexes_test-index_task_228603771246-fa7feea5-229a-49af-ac9d-f4754539eb55.json
1_indexes_test-index_task_228603771246-fb081872-590e-443a-8c8b-957ac58fa541.json
1_indexes_test-index_task_228603771246-ff880e8d-f1cf-43ad-9e21-dfe5dab7a3c7.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… OK that is quite a lot of files. 😅 As mentioned earlier, a lot of them are about polling.&lt;/p&gt;

&lt;p&gt;Let's find the one that we should keep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; published testdata/&lt;span class="k"&gt;*&lt;/span&gt;task&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="go"&gt;
testdata/1_indexes_test-index_task_228603771246-0004a7a6-3759-452c-8d0d-ea2a2f948ea1.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
testdata/1_indexes_test-index_task_228603771246-012f5d71-2645-496c-ba68-b85dcafbce51.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
…
testdata/1_indexes_test-index_task_228603771246-3ba76a13-6052-4b9d-a7f2-61d3d364714d.json:    "body" : "{\"status\":\"published\",\"pendingTask\":false}",
…
testdata/1_indexes_test-index_task_228603771246-fb081872-590e-443a-8c8b-957ac58fa541.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
testdata/1_indexes_test-index_task_228603771246-ff880e8d-f1cf-43ad-9e21-dfe5dab7a3c7.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After removing the files with &lt;code&gt;notPublished&lt;/code&gt;, we are left with the following mapping files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; testdata
&lt;span class="go"&gt;1_indexes_test-index-4c8f560d-010a-4db9-916c-f5c112481fc8.json
1_indexes_test-index-e766b4bf-6087-499b-975f-722c451f1d4a.json
1_indexes_test-index_query-e08439bf-fd60-4fac-8181-11b72f7a7a1c.json
1_indexes_test-index_settings-9de043d2-5803-40a0-b19b-49d0d2a17dde.json
1_indexes_test-index_task_228603771246-3ba76a13-6052-4b9d-a7f2-61d3d364714d.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Switching to playback mode 📺
&lt;/h3&gt;

&lt;p&gt;Now that we have our mapping files, we can switch to playback mode. Let us introduce a constant to turn recording and Algolia debugging on and off:&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;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;// …&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enable&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;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://%s-dsn.algolia.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to start recording: %v"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopRecording&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to stop recording: %v"&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="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;Note that I also moved the call to &lt;code&gt;debug.Enable()&lt;/code&gt; to the recording block, when replaying the tests, we do not really need to clutter the output with Algolia debug information.&lt;/p&gt;

&lt;p&gt;And now the test fails, with a rather clear error: apparently deleting the files was not enough, and we need to also edit the scenario name to outline that this is no longer the 43rd attempt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- FAIL: TestIndexRecord (1.87s)
panic: API error [404]
                                                       Request was not matched
                                                       =======================

        -----------------------------------------------------------------------------------------------------------------------
        | Closest stub                                             | Request                                                  |
        -----------------------------------------------------------------------------------------------------------------------
                                                                   |
        1_indexes_test-index_task_226434943725                     |
                                                                   |
        GET                                                        | GET
        /1/indexes/test-index/task/226434943725                    | /1/indexes/test-index/task/226434943725
                                                                   |
        [Scenario                                                  | [Scenario                                           &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; Scenario does not match
        'scenario-1-1-indexes-test-index-task-226434943725'        | 'scenario-1-1-indexes-test-index-task-226434943725'
        state:                                                     | state: Started]
        scenario-1-1-indexes-test-index-task-226434943725-43]      |
                                                                   |
        -----------------------------------------------------------------------------------------------------------------------
         [recovered]
        panic: API error [404]
                                                       Request was not matched
                                                       =======================

        -----------------------------------------------------------------------------------------------------------------------
        | Closest stub                                             | Request                                                  |
        -----------------------------------------------------------------------------------------------------------------------
                                                                   |
        1_indexes_test-index_task_226434943725                     |
                                                                   |
        GET                                                        | GET
        /1/indexes/test-index/task/226434943725                    | /1/indexes/test-index/task/226434943725
                                                                   |
        [Scenario                                                  | [Scenario                                           &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; Scenario does not match
        'scenario-1-1-indexes-test-index-task-226434943725'        | 'scenario-1-1-indexes-test-index-task-226434943725'
        state:                                                     | state: Started]
        scenario-1-1-indexes-test-index-task-226434943725-43]      |
                                                                   |
        -----------------------------------------------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After dropping &lt;code&gt;"requiredScenarioState" : "scenario-1-1-indexes-test-index-task-226434943725-43",&lt;/code&gt; from the mapping file about polling, the test passes again, only this time, it passes in under 2 seconds.&lt;br&gt;
It is possible to mention which scenario a mapping belongs to, allowing to do things like "On the first 2 calls respond A, and on the 3rd return B". Based on that, it is possible to build a complex choreography of requests/responses, fulfilling all sorts of requirements.&lt;/p&gt;
&lt;h3&gt;
  
  
  Making it work in the CI
&lt;/h3&gt;

&lt;p&gt;After pushing the code, I got a bad surprise: the test fails in the CI, with the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tc-wiremock.go:73: create container: container create: Error response from daemon: Invalid bind mount config: mount source "/builds/product-discovery/ms.indexer/internal/import/brandsuggestion/testdata" is forbidden by the allow list [/home /tmp] - update the bind mounts configuration and restart the agent to enable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would seem that we cannot use a bind mount in the CI. Let us use our &lt;code&gt;record&lt;/code&gt; constant to make the container options conditional:&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;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use a bind mount&lt;/span&gt;
    &lt;span class="n"&gt;opts&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;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHostConfigModifier&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;hostConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;hostConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Binds&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata:/home/wiremock/mappings"&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use a copy operation&lt;/span&gt;
    &lt;span class="n"&gt;mappingFiles&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read testdata directory: %v"&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;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mappingFile&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;mappingFiles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opts&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;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMappingFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s"&gt;"testdata/"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When recording, we mount the volume, which is not an issue because we are not in the CI.&lt;br&gt;
Otherwise, we use the &lt;code&gt;WithMappingFile&lt;/code&gt; function which relies on a copy operation.&lt;br&gt;
&lt;a href="https://pkg.go.dev/github.com/wiremock/wiremock-testcontainers-go#WithMappingFile" rel="noopener noreferrer"&gt;That function&lt;/a&gt; is provided by the&lt;br&gt;
&lt;code&gt;wiremock-testcontainers-go&lt;/code&gt; library, which abstracts away the low-level testcontainers API so that we can think in terms of mapping files rather than just JSON files.&lt;/p&gt;

&lt;p&gt;Not super satisfying, but it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The test is a bit long now, but some parts look generic and reusable. Let us extract them to helpers.&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;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;indexer&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/call"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/debug"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/search"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/transport"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/docker/docker/api/types/container"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/testcontainers/testcontainers-go"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/wiremock/go-wiremock"&lt;/span&gt;
    &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt; &lt;span class="s"&gt;"github.com/wiremock/wiremock-testcontainers-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;spinUpContainer&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="kt"&gt;string&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;Helper&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;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;absolutePath&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getwd&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get current working directory: %v"&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;var&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerCustomizer&lt;/span&gt;

    &lt;span class="n"&gt;opts&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;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"wiremock/wiremock:3.12.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opts&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;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHostConfigModifier&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;hostConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;hostConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Binds&lt;/span&gt; &lt;span class="o"&gt;=&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="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata:/home/wiremock/mappings"&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mappingFiles&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;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read testdata directory: %v"&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;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mappingFile&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;mappingFiles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;opts&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;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMappingFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s"&gt;"testdata/"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&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;container&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;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunContainerAndStopOnCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&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;opts&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create wiremock container: %v"&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="n"&gt;host&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;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get wiremock container endpoint: %v"&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;host&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;startRecording&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="n"&gt;host&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;appID&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;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;wiremockClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;host&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;Cleanup&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to reset wiremock: %v"&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="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;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enable&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;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://%s-dsn.algolia.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to start recording: %v"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopRecording&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to stop recording: %v"&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="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;newTestClient&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="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIClient&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;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;algoliaClient&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;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClientWithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchConfiguration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="o"&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;Configuration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AppID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Hosts&lt;/span&gt;&lt;span class="o"&gt;:&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;StatefulHost&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;NewStatefulHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&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;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="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create client: %v"&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;algoliaClient&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;TestIndexRecord&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="n"&gt;appID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;spinUpContainer&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;startRecording&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;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;algoliaClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newTestClient&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;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&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;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSetSettingsRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"test-index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptyIndexSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;SetSearchableAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&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="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to set settings: %v"&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&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="n"&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;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiDeleteIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to delete index: %v"&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="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;searchResp&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;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchSingleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSearchSingleIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;WithSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchParamsObjectAsSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptySearchParamsObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                        &lt;span class="n"&gt;SetQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&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="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;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&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;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No hits found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;firstHit&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c"&gt;// Assert that the first hit has the expected name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;firstHit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdditionalProperties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"test record"&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;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Expected name to be 'test record', got '%s'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;firstHit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdditionalProperties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now our test fits on a single screen 🙂&lt;br&gt;
I also added an extra assertion just to be sure we get the expected record, and that's OK, since it does not mean extra calls to Algolia.&lt;br&gt;
Now that we have paid the cost of writing that first step, writing more tests should be easier, and bring a lot of value to the project.&lt;/p&gt;

</description>
      <category>go</category>
      <category>wiremock</category>
      <category>algolia</category>
      <category>testcontainers</category>
    </item>
    <item>
      <title>Why you should stop doing code reviews!</title>
      <dc:creator>Victor Gallet</dc:creator>
      <pubDate>Wed, 03 Jan 2024 13:32:00 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/why-you-should-stop-doing-code-reviews-lme</link>
      <guid>https://forem.com/manomano-tech-team/why-you-should-stop-doing-code-reviews-lme</guid>
      <description>&lt;p&gt;&lt;em&gt;I would like to thank Baptiste Barbieri, Antoine Guy, Maxence Zajdlic and Rafael S. Ward for their time &amp;lt;3.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Code review is one of the most used methods in software development. We all know what are the benefits of doing code review.&lt;/p&gt;

&lt;p&gt;To sum up, doing code review enables verification of new code quality by identifying future bugs, ensuring tested code, and ensuring readable code.&lt;/p&gt;

&lt;p&gt;Moreover, by reading code written by others, developers can learn from others. A junior developer can learn by reading the code of a more experienced one. They can ask questions using comments.&lt;br&gt;
Vice versa a senior developer can check the code of a junior developer and provide constructive feedback.&lt;/p&gt;

&lt;p&gt;I won’t go deeper into code review benefits in this article. For more information, you can read the amazing work done by Trisha Gee. For instance &lt;a href="https://trishagee.github.io/post/code_review_best_practices/"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, is code review really worth doing? What are the drawbacks we faced doing code review? I tried to list all code review antipatterns I faced and I’m sure you faced some of them. Several times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code review pitfalls
&lt;/h2&gt;

&lt;h4&gt;
  
  
  The “ping pong” review
&lt;/h4&gt;

&lt;p&gt;Some comments are added, you take them into account, some new comments are added, you take them into account, new comments are added…and so on. It’s really frustrating for the merge request author, even more when it happens with several reviewers.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “re-design” review
&lt;/h4&gt;

&lt;p&gt;Once the merge request opens, you receive comments indicating a large design issue. It’s not a simple correction, but the whole implementation must be redone. This is a demoralizing situation for the author and the reviewers and a waste of time.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “ghosting” review
&lt;/h4&gt;

&lt;p&gt;Comments are added, taken into account and then you wait for the approval. The reviewer has disappeared and you have to wait before merging. Even if other reviewers have approved the merge request, you have to wait for the ghost reviewer.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “waiting for” review
&lt;/h4&gt;

&lt;p&gt;Your merge request is not the most attractive and you are still awaiting approval.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “TL;DR” review
&lt;/h4&gt;

&lt;p&gt;Changes are very important but reviewers didn’t read it before approving it because it was too long.&lt;/p&gt;

&lt;h4&gt;
  
  
  The presentation review
&lt;/h4&gt;

&lt;p&gt;You have done a huge refactoring or the changes you bring are really complex to review and your team is asking you for a presentation or a mob review. Now you have to find a time where everyone is available to do the code review, it’s only the beginning of your merge request journey.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “convention style” review
&lt;/h4&gt;

&lt;p&gt;Some comments on the merge request are merely about coding style. For instance, there are too many break lines or spaces. Those kinds of reviews could be avoided with a linter.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “too small” merge request
&lt;/h4&gt;

&lt;p&gt;If one line of code has been modified, you tend to think that it will be approved and merged fast. BUT! Instead, it triggers a lot of discussions. We are typically in a &lt;a href="https://en.wikipedia.org/wiki/Law_of_triviality"&gt;bike-shed effect&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “keep rebasing” review
&lt;/h4&gt;

&lt;p&gt;Your merge request is waiting for approval, while other merge requests are merged and you have to keep your code up to date and potentially resolve conflicts.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “inconsistent feedback” review
&lt;/h4&gt;

&lt;p&gt;Sometimes you get a comment and don’t know how to handle it. Most of the time, the review expressed a feeling more than a clear solution. For example “This function seems too complicated for me”.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “after merged” review
&lt;/h4&gt;

&lt;p&gt;The merge request has been approved and merged. Great! But a new challenger has appeared! This challenger is perhaps knowledgeable about the project or on the technologies used and he wants to make some changes. Most of the time, a new merge request will be opened by this person without sharing it with the first developer.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “conflict” review
&lt;/h4&gt;

&lt;p&gt;Expressing or receiving feedback can be complicated, people can take it personally and it can lead to some tensions in the team.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “elderly” merge request
&lt;/h4&gt;

&lt;p&gt;The merge request is open for a long time, it could be a couple of weeks, a month or even a year. The merge request is so old that most of the developers don’t know if it’s still relevant to merge it so the merge request will just sit there.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “not working” review
&lt;/h4&gt;

&lt;p&gt;The CI pipeline passed, and the merge request has been approved but once merged and deployed... it doesn’t work! Let’s do a full cycle again.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “Full-time job” review
&lt;/h4&gt;

&lt;p&gt;Code review can be a full-time job! A developer in a team could spend their entire time doing code review. Furthermore, it’s not uncommon to have developers in a team who are spending way more time than others doing code reviews. Not everyone does code review in a balanced way and it can lead to struggles within the team.&lt;/p&gt;

&lt;p&gt;The worst thing about all those patterns is that they can be accumulated. You can be in a ping pong review, suddenly land on with a ghost review, keep rebasing and finish by having some tensions in your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop doing code review?
&lt;/h2&gt;

&lt;p&gt;So what are the solutions to avoid those situations? Most of these problems could be avoided by sharing early. Before what?&lt;br&gt;
For instance, sharing about coding guidelines, architecture, coding practices or design before jumping into the code. All of these methods and practices are living, moving with time and have to be updated during the project lifecycle.&lt;/p&gt;

&lt;p&gt;It’s merely an Agile value: having feedback as soon as possible. Feedback coming from code review is too late.&lt;/p&gt;

&lt;p&gt;In my experience, code review creates more problems than it solves. Code review is only good when developers work mostly in an asynchronous way and in different time zones. But in most cases, developers are working in the same team, on the same project at the same time and code review is only a way to avoid exchanges with colleagues. Nowadays, code review is a cargo cult.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of the time&lt;/strong&gt; code review should be replaced by pair programming or better still ensemble programming (also known as mob programming).&lt;br&gt;
In that way, you have at least two people working on the same piece of code and you always have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;knowledge sharing, both business and technical knowledge&lt;/li&gt;
&lt;li&gt;proof-reading&lt;/li&gt;
&lt;li&gt;developing cohesion and team spirit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing pair programming, doing synchronous development saves from ghosting, from ping pong comments, “TL;DR” review, “waiting for” review, “convention style” review….and so on. Actually, working together avoids all of the pitfalls of code review.&lt;/p&gt;

&lt;p&gt;If you are developing using TDD, pair programming can even be more fun by doing &lt;a href="https://anthonysciamanna.com/2015/04/18/ping-pong-pair-programming.html"&gt;ping pong pair programming&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, I won’t debate about the simplistic idea that having two developers working together on the same task is more expensive compared to having them working alone. The synchronisation process in all steps is one of the most expensive and code review is one of them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://vgallet.github.io"&gt;https://vgallet.github.io&lt;/a&gt; on December 14, 2023.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>programming</category>
      <category>productivity</category>
      <category>development</category>
    </item>
    <item>
      <title>Feedback on sfCon 2022</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Thu, 15 Dec 2022 10:18:34 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/feedback-on-sfcon-2022-1pe5</link>
      <guid>https://forem.com/manomano-tech-team/feedback-on-sfcon-2022-1pe5</guid>
      <description>&lt;p&gt;This year, the &lt;a href="https://live.symfony.com/2022-paris-con/"&gt;SymfonyCon&lt;/a&gt; was even more magical than it usually is, as it took place in Disneyland Paris, thus contributing sparkles on top of the nice features announced during the keynote.&lt;/p&gt;

&lt;p&gt;Did you know that Symfony currently has no less than roughly 200 components? At the SymfonyCon, 2 new components were announced, that should make it into Symfony 6.3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhook: can be used to host a webhook, or call a webhook, typically with payment providers. Handles validity, security and signature checks.&lt;/li&gt;
&lt;li&gt;RemoteEvent: used to represent events produced outside Symfony&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/symfony/symfony/pull/48542/files"&gt;the diff&lt;/a&gt; if you want to deep-dive head first! &lt;/p&gt;

&lt;p&gt;This is a big event, so there were no less than 4 tracks, which means I had to choose.&lt;/p&gt;

&lt;p&gt;First, I went to see a conference about Sylius and learnings the main maintainer has gathered over the past few years.&lt;br&gt;
He mentioned &lt;a href="https://dev.to/kevinjalbert/start-now-architecture-decision-records-580"&gt;ADRs&lt;/a&gt;, which we do use at ManoMano, and which would certainly be useful to also use in an open source setup (Doctrine maybe?).&lt;/p&gt;

&lt;p&gt;Next, I went to see Nicolas Grekas' talk about proxies: he presented different ways to implement a proxy system, and examples of each one that can be found in Symfony or Doctrine.&lt;br&gt;
Doctrine has an old implementation in &lt;code&gt;doctrine/common&lt;/code&gt; that we are trying to get rid off, and &lt;a href="https://github.com/doctrine/orm/pull/10187"&gt;Nicolas has proposed to replace those with Lazy Ghost proxies&lt;/a&gt;.&lt;br&gt;
I know, I know, funny name. 👻&lt;br&gt;
Lazy ghost proxies cannot be used with final classes, or internal classes, but do allow to use a fluent API, which makes them a good fit for &lt;code&gt;doctrine/orm&lt;/code&gt; entities.&lt;/p&gt;

&lt;p&gt;If you know me, you know I literally cannot shut up about Git, so for my next choice, I picked "advanced git magic" by Pauline Vos.&lt;br&gt;
The highlight was definitely this explanation about &lt;code&gt;git bisect&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Be4wDaV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/358/863/606/123/532/original/1c18e0aa60623d30.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Be4wDaV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/358/863/606/123/532/original/1c18e0aa60623d30.jpeg" alt="a slide with an egg-and-building-based metaphor" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After lunch, I heard there was a talk by one of my Doctrine fellows, Claudio Zizza a.k.a SenseException, which I first met in person at that conference, so of course, I &lt;em&gt;had&lt;/em&gt; to pick that one.&lt;br&gt;
Main takeways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always clear your entity manager at the end of a business class to avoid nasty side effects with &lt;code&gt;$entityManager-&amp;gt;clear()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;(Postgre)SQL is very powerful, for instance you don't necessarily need to resort to &lt;a href="https://en.wikipedia.org/wiki/Nested_set_model"&gt;the nested set model&lt;/a&gt; to build a menu, you can use &lt;code&gt;WITH RECURSIVE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A1If5rN2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/359/416/519/744/144/original/f0eed52a852e86b3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A1If5rN2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/359/416/519/744/144/original/f0eed52a852e86b3.jpeg" alt="Claudio and big Doctrine logo" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was followed by a frightening talk form Sebastian Bergmann (himself!), who gave us a presentation of PHP's supply chain, and how each part of that chain has been exploited in the past, in PHP or for other ecosystems.&lt;br&gt;
Some of these vulnerabilities are so bad they have got their own website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://meltdownattack.com/"&gt;https://meltdownattack.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://heartbleed.com/"&gt;https://heartbleed.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hertzbleed.com/"&gt;https://www.hertzbleed.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cuteboi.info/"&gt;https://cuteboi.info/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the links above are safe to visit, I swear! That last one might require a bit more processing power though.&lt;/p&gt;

&lt;p&gt;Sebastian told us about the concept of Software bill of materials a.k.a. SBOM, as a way for you to know your dependencies and if they are vulnerable. You can try &lt;code&gt;./phpunit.phar --sbom&lt;/code&gt; to see it in action (only available when using the PHAR though).&lt;/p&gt;

&lt;p&gt;I had already seen Kevin Dunglas' talk about &lt;a href="https://frankenphp.dev"&gt;FrankenPHP&lt;/a&gt; at the "Forum PHP", which also took place at Disneyland the previous month (yes, really), but I decided to watch it again because of how packed with information it was.&lt;br&gt;
FrankenPHP provides a new PHP &lt;a href="https://en.wikipedia.org/wiki/Server_application_programming_interface"&gt;SAPI&lt;/a&gt;, that comes with a worker mode, that behaves much like Swoole or Roadrunner.&lt;br&gt;
Unlike php-fpm, it does not require a separate nginx container to run: it's built on top of &lt;a href="https://caddyserver.com/"&gt;the Caddy webserver&lt;/a&gt;, which is written in Go, and is also written in Go.&lt;br&gt;
Thanks to Kévin, Caddy is capable of forwarding 1xx HTTP responses it receives from its upstream, which unlocks using 103 Early Hints in PHP, a status code that aims at providing the browser with the urls of CSS files, JS files and other resources it might need before even sending the final response.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1557064869035347969-206" src="https://platform.twitter.com/embed/Tweet.html?id=1557064869035347969"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1557064869035347969-206');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1557064869035347969&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Next, I went on a conference about the impostor syndrome, and one of the main takeaway was that it's so common and equally spread that we should rather call it impostor phenomenon. It's estimated to be have been experienced by 70% of people (and yes, that includes me). If you're unfamiliar with it, you can tell yourself that it is close to atychiphobia or kakorraphiophobia. Hope this helps.&lt;/p&gt;

&lt;p&gt;That concluded the first day of conference. The 2 days were separated by a very nice evening with exclusive access to the Walt Disney Studios Park, which I must say is amazing, but I'll stop here because I don't want to make you jealous. But it was cool.&lt;/p&gt;

&lt;p&gt;On the second day, I started with a conference about climate change and IT. As a staff engineer, I'm currently working on upgrading many of our microservices from PHP 7 to PHP 8, and I was glad to learn that PHP 8.1 is estimated to be 30% faster/less electricity-consuming than PHP 7.4.&lt;br&gt;
There wasn't a similar figure to share about Symfony because it's harder to determine, but upgrading it is highly recommended as well.&lt;br&gt;
I learnt that the carbon footprint can be evaluated in several scope, and that for an IT company like ManoMano, most of it is in Scope 3, which is all about the cloud, Scope 1 and 2 being about office heating and electricity. To lower&lt;br&gt;
our impact, we should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upgrade our software&lt;/li&gt;
&lt;li&gt;Optimize our apps (with a profiler)&lt;/li&gt;
&lt;li&gt;Deploy to greener AWS regions: France, the Scandinavian peninsula (or &lt;a href="https://youtu.be/TsXMe8H6iyc?t=37"&gt;Fennoscandia&lt;/a&gt; for geography nerds), the Netherlands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was very interesting, so I continued with a talk on the same theme: it was about computing carbon emissions in the cloud.&lt;br&gt;
I learnt that "net zero" means the emissions of green house gases is equal to the removals, and that we are of course, very far from it, although it's a goal for 2050, that gets harder and harder to reach as time passes.&lt;br&gt;
There is a formula to compute the carbon footprint of a VM. For each capacity, such as the CPU, storage, network, you have a matching emission factor expressed in watts, and a Power Usage Effectiveness that depends on the cloud provider you are using, which is a constant &amp;gt; 1 that is better the closest it is to 1. You then multiply that by the carbon intensity, that depends on the region where you are deploying, and is a factor that allows you to transform watts in CO2 emissions.&lt;/p&gt;

&lt;p&gt;As the conference was getting closer to its end, I attended a talk about TDD.&lt;br&gt;
The speaker dove into detail about TDD and gave us pointers about methods to do it properly, such as "fake it till you make it", which means you should write dummy code until you have written enough tests that force you to ditch your&lt;br&gt;
initial, dummy implementation, or "triangulation", which consists in proving via tests that all parameters of a function are important, one by one.&lt;/p&gt;

&lt;p&gt;Next we had a talk about automated upgrades by Tomáš Votruba, the author of &lt;a href="https://github.com/rectorphp/rector"&gt;Rector&lt;/a&gt;. He told us about tooling you can have in your projects to make it easier to upgrade. For instance, you can use so-called PHPStan collectors to measure type coverage, and ensure it only ever increases. Having type information is very important for static analysis, which Rector relies upon to understand your code and migrate it. Likewise, using PHP configuration instead of YAML configuration can help avoiding mistakes thanks to Static Analysis (which exists for PHP but not for YAML). He showed us a demo of using Rector in the CI to continuously upgrade the code.&lt;/p&gt;

&lt;p&gt;The next talk I attended was by another fellow Doctrine maintainer: Alexander Turek. Alexander showed us strategies to modernize a legacy application with Symfony. It's possible for instance to introduce a global variable with the Symfony Kernel inside it to make Symfony services available inside the legacy application. That's called a micro rewrite because instead of migrating a full route to Symfony, you can migrate a service, deploy, rinse and repeat.&lt;br&gt;
Something we might want to apply to our legacy ManoMano application?&lt;/p&gt;

&lt;p&gt;The last talk I attended was by Mathieu Santostefano, a core team member. He presented the new &lt;code&gt;AccessTokenAuthenticator&lt;/code&gt; shipped with Symfony 6.2, explaining how it had been waiting for other lower-level Symfony components to be ready over the years. Part of the talk was about the contribution process, and I remember a hilarious slide showing 2 contributors opening 2 pull requests about the very same thing (implementing that authenticator).&lt;/p&gt;

&lt;p&gt;For the closing keynote, we had a light-hearted talk by Mickey Mouse himself presenting the evolution of Symfony over the past few months!&lt;/p&gt;

&lt;p&gt;If this feedback piqued your interest, you can find many of the talks here: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SymfonyCon"&gt;
        SymfonyCon
      &lt;/a&gt; / &lt;a href="https://github.com/SymfonyCon/2022-talks"&gt;
        2022-talks
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
&lt;a href="https://live.symfony.com/2022-paris-con/" rel="nofollow"&gt;SymfonyCon - Paris 2022&lt;/a&gt; talks&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;All talks are in &lt;strong&gt;english&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You can send feedback and love to speakers on their twitter account&lt;/li&gt;
&lt;li&gt;See &lt;a href="https://twitter.com/search?q=(%23symfonycon%20OR%20%23symfonycon2022)%20until%3A2022-11-19%20since%3A2022-11-17%20-%23LuckyWebby&amp;amp;src=typed_query&amp;amp;f=top" rel="nofollow"&gt;all tweets&lt;/a&gt; during the event&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Keynote&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://speakerdeck.com/fabpot/symfonycon-2022-keynote-webhooks" rel="nofollow"&gt;Slides&lt;/a&gt;&lt;br&gt;
&lt;del&gt;Video&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;By &lt;a href="https://connect.symfony.com/profile/fabpot" rel="nofollow"&gt;Fabien Potencier&lt;/a&gt;&lt;br&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/SymfonyCon/2022-talksicon/github.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vVfKhQVi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/SymfonyCon/2022-talksicon/github.png" alt="github"&gt;&lt;/a&gt; &lt;a href="https://github.com/fabpot"&gt;@fabpot&lt;/a&gt;&lt;br&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/SymfonyCon/2022-talksicon/twitter.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XyFF_z0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/SymfonyCon/2022-talksicon/twitter.png" alt="twitter"&gt;&lt;/a&gt; &lt;a href="https://twitter.com/fabpot" rel="nofollow"&gt;@fabpot&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
From monolith to decoupled…wait, why is that one getting bigger?!?&lt;/h2&gt;
&lt;dl&gt;
  &lt;dt&gt;Description&lt;/dt&gt;
  &lt;dd&gt;Monolithic apps are getting broken down left and right into dedicated services and teams, all under the banners of separating concerns, higher efficiency, and more. The strategy that is implemented will be crucial to ensure decoupling is a beacon of efficiency and not a migration nightmare
&lt;p&gt;In this talk, we’ll discuss how decoupling following the strangler fig approach will seem counter-intuitive, as your monolith continues to grow alongside your new decoupled architecture. But this approach, when done right, makes dismantling a monolith a process that is structured and safe, slow but agile, and without major service interruptions or massive interface changes that…&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SymfonyCon/2022-talks"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Plotting the memory of a PHP process with Gnuplot 📈</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Fri, 26 Aug 2022 14:52:33 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/plotting-the-memory-of-a-php-process-with-gnuplot-3k02</link>
      <guid>https://forem.com/manomano-tech-team/plotting-the-memory-of-a-php-process-with-gnuplot-3k02</guid>
      <description>&lt;p&gt;Lately, I have been troubleshooting memory issues on a process my team owns.&lt;br&gt;
I started by watching this video about memory leaks from Benoît Jacquemont: &lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wZjnj1PAJ78"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I learned a lot in the process, but also noticed the nice memory graphs in the video and figured it would be hard to troubleshoot anything if I didn't have them. &lt;br&gt;
When using php extensions such as &lt;a href="https://github.com/BitOne/php-meminfo" rel="noopener noreferrer"&gt;Benoît's&lt;/a&gt; or &lt;a href="https://github.com/arnaud-lb/php-memory-profiler" rel="noopener noreferrer"&gt;Arnaud Le Blanc's&lt;/a&gt; to take a snapshot of the memory, it's great to think about the most appropriate moment to take that snapshot in order to capture the memory leak you might be hunting.&lt;br&gt;
Sure, you can use Monolog's &lt;a href="https://seldaek.github.io/monolog/doc/02-handlers-formatters-processors.html" rel="noopener noreferrer"&gt;MemoryUsageProcessor&lt;/a&gt; to that end, but I thought it would be more useful to get something a bit more ✨visual✨.&lt;/p&gt;

&lt;p&gt;On our environments, we use Datadog, but on my development setup, I don't have that.&lt;/p&gt;

&lt;p&gt;There are several metrics you can track for troubleshooting a memory issue, some provided by the OS, and typically reported by Datadog, some others by PHP through &lt;code&gt;memory_get_usage()&lt;/code&gt; (which I don't currently have a way to monitor in production).&lt;/p&gt;
&lt;h1&gt;
  
  
  Measuring from PHP
&lt;/h1&gt;

&lt;p&gt;PHP provides several methods to understand what is happening memory-wise. First, you have &lt;code&gt;memory_get_usage()&lt;/code&gt;, which takes a boolean argument. Depending on that argument, the method returns the memory used by PHP, or the memory allocated to PHP. When freeing some memory, you will typically see the former decrease, while the latter stays stable.&lt;/p&gt;

&lt;p&gt;Then, you have &lt;code&gt;memory_get_peak_usage()&lt;/code&gt; which reports the highest value of used or allocated memory since the beginning of the script. That's useful because it can help the developer figure out that they are not calling &lt;code&gt;memory_get_usage()&lt;/code&gt; where memory usage is at its highest.&lt;/p&gt;
&lt;h2&gt;
  
  
  Producing metrics
&lt;/h2&gt;

&lt;p&gt;In the case of the script I was troubleshooting, I had a main loop that was frequently executed (but not at an even rate). That's still a good candidate for gathering metrics, as we will see.&lt;/p&gt;

&lt;p&gt;Here is what I put inside that loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'memory.tsv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;FILE_APPEND&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a TSV file that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1660831187  20  8.1252593994141 20.359375   19.608978271484
1660831187  20  8.1281814575195 20.359375   19.608978271484
1660831187  20  8.131103515625  20.359375   19.608978271484
1660831187  20  8.134033203125  20.359375   19.608978271484
1660831187  20  8.1369552612305 20.359375   19.608978271484
1660831187  20  8.1398773193359 20.359375   19.608978271484
1660831190  22  8.2328033447266 24.42578125 22.869613647461
1660831190  22  8.2357330322266 24.42578125 22.869613647461
1660831190  22  8.2386627197266 24.42578125 22.869613647461
1660831190  22  8.2415924072266 24.42578125 22.869613647461
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the first column, what stands out is that there are groups of lines that can be several seconds apart, so the production of metrics is really, really not paced regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting graphs
&lt;/h2&gt;

&lt;p&gt;Then, to create the graph, I turned to &lt;a href="http://www.gnuplot.info" rel="noopener noreferrer"&gt;gnuplot&lt;/a&gt;, which seems like a whole universe of its own as well as a very robust piece of software. I started by creating the following configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config.plt
set term png small size 800,600
set output "/tmp/memory_get_usage-graph.png"

set ylabel "memory in MB"

set yrange [0:*]

set xdata time # x is not just a random number
set timefmt "%s" # we use UNIX timestamps

plot "memory.tsv" using 1:2 with lines axes x1y1 title "memory_get_usage(true) in MB", \
     "memory.tsv" using 1:3 with lines axes x1y1 title "memory_get_usage(false) in MB", \
     "memory.tsv" using 1:4 with lines axes x1y1 title "memory_get_peak_usage(true) in MB", \
     "memory.tsv" using 1:5 with lines axes x1y1 title "memory_get_peak_usage(false) in MB"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it's possible to let gnuplot know that the x axis represents time, which ensures you have a nicely formatted X axis.&lt;/p&gt;

&lt;p&gt;The graph is created by running &lt;code&gt;gnuplot config.plt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering the graph
&lt;/h2&gt;

&lt;p&gt;What would be handy would be a graph that refreshes over time.  For that, you will need 2 tiny programs: &lt;code&gt;watch&lt;/code&gt; and &lt;a href="https://feh.finalrewind.org" rel="noopener noreferrer"&gt;&lt;code&gt;feh&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;watch gnuplot config.plt&lt;/code&gt;, to ensure a png is created every 2 seconds (which is watch’s default).&lt;br&gt;
In parallel of that, you run &lt;code&gt;feh /tmp/memory_get_usage-graph.png&lt;/code&gt; to display the png file. What's great with &lt;code&gt;feh&lt;/code&gt; is that it refreshes automatically, so you don't need to do anything special to get your live graph. 🤯 &lt;code&gt;feh&lt;/code&gt; does very little, but does it well.&lt;/p&gt;

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

&lt;p&gt;2 interesting things to note here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the graph for &lt;code&gt;memory_get_usage(false)&lt;/code&gt; goes down, but it does go down, so there is no memory leak&lt;/li&gt;
&lt;li&gt;The defaults of gnuplot are a bit ugly, and I am no frontend developer, so it will stay ugly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Measuring from Linux
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Producing metrics
&lt;/h2&gt;

&lt;p&gt;Here, to produce the metrics, you can use &lt;code&gt;ps&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;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;ps &lt;span class="nt"&gt;--pid&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; some_string_that_identifies_your_process&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,%mem&lt;span class="o"&gt;=&lt;/span&gt;,vsz&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /tmp/mem.log
    gnuplot config.plt
    &lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you can of course use this for any process, not just PHP processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting graphs
&lt;/h2&gt;

&lt;p&gt;This time it's a bit more tricky, I am telling gnuplot to plot 2 metrics that&lt;br&gt;
have different units on the same graph.&lt;/p&gt;

&lt;p&gt;The left Y axis will have a scale for the first metric, and the right Y axis&lt;br&gt;
will have a scale for the second metric.&lt;/p&gt;

&lt;p&gt;I do not configure the X axis this time, since I'm producing metrics at a&lt;br&gt;
regular pace.&lt;/p&gt;

&lt;p&gt;This is all shamelessly stolen from &lt;a href="https://stackoverflow.com/questions/7998302/graphing-a-processs-memory-usage/12595110#12595110" rel="noopener noreferrer"&gt;Stack Overflow&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set term png small size 800,600
set output "/tmp/mem-graph.png"

set ylabel "VSZ"
set y2label "%MEM"

set ytics nomirror
set y2tics nomirror in

set yrange [0:*]
set y2range [0:*]

plot "/tmp/mem.log" using 3 with lines axes x1y1 title "VSZ", \
     "/tmp/mem.log" using 2 with lines axes x1y2 title "%MEM"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Here you can see that the figures are different than from inside PHP. I will not get into this because that is off topic, but when troubleshooting memory issues, it can also be important to compare both aspects.&lt;/p&gt;

&lt;h1&gt;
  
  
  Takeaway
&lt;/h1&gt;

&lt;p&gt;Those graphs helped me understand the differences between &lt;code&gt;memory_get_usage(true)&lt;/code&gt; and &lt;code&gt;memory_get_usage(false)&lt;/code&gt;, and gave me a better understanding of my application. In particular, I understood that the batch processing I was doing relied on batches of objects that were not all the same size, and that making sure they were all roughly the same size would help avoid situations where a series of big objects caused an out-of-memory error.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>memory</category>
      <category>gnuplot</category>
    </item>
    <item>
      <title>How to work with redux on module federation</title>
      <dc:creator>Danielo Artola</dc:creator>
      <pubDate>Thu, 17 Feb 2022 15:14:17 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/how-to-work-with-redux-on-module-federation-13gm</link>
      <guid>https://forem.com/manomano-tech-team/how-to-work-with-redux-on-module-federation-13gm</guid>
      <description>&lt;p&gt;Module Federation is a plugin from Webpack 5 that helps to orchestrate micro-frontend architecture, thereby making it easier for organizational teams to decouple and share applications.&lt;/p&gt;

&lt;p&gt;If you have a big monolith on frontend with react, you probably have redux to share info between components. What happens if we have to move some of those components to a module?&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%2F1sod1o3008dazl1igbqy.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%2F1sod1o3008dazl1igbqy.png" alt="Redux on module federation" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This post discusses how to share info between modules via redux, so we’ll focus on how modules and base will work with the store, reducers, and actions. Also, the following sections will explain how modules can work with redux as isolated applications or a federated module with minimal code changes.&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%2F3a7dm28hm0et3yz2tzz7.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%2F3a7dm28hm0et3yz2tzz7.png" alt="Basic architecture schema" width="697" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Basic app architecture&lt;/li&gt;
&lt;li&gt;Store for base and modules&lt;/li&gt;
&lt;li&gt;Trigger an action from module&lt;/li&gt;
&lt;li&gt;Inject reducer from module to base&lt;/li&gt;
&lt;li&gt;
Conclusions &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Basic app architecture
&lt;/h2&gt;

&lt;p&gt;In general, this approach consist of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A NextJS project as a host/base project.&lt;/li&gt;
&lt;li&gt;Federated modules for some features, such as Header, Homepage, Cart, and ProductPage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep in mind that for this example we’ll be working with an NextJS base (React, Redux, etc) and a module with the same technologies (which exports only one component).&lt;/p&gt;

&lt;p&gt;Our module will consist of a web app with react and redux, without NextJS, but the main point here will be only how to interact with redux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Store for base and modules
&lt;/h2&gt;

&lt;p&gt;The idea here is that all modules should work as an isolated app and as a federated module. Furthermore, we know redux actions can only be triggered under a redux provider.&lt;/p&gt;

&lt;p&gt;Therefore, the strategy is to place a provider on sub apps, but export only the components under this provider to inject the module on our base.&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%2Fw7lkvqzt8agz2lilxnp8.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%2Fw7lkvqzt8agz2lilxnp8.png" alt="export only component under provider" width="621" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is to always have a redux provider, but avoid having two providers when a module is injected.&lt;/p&gt;

&lt;p&gt;With this we’ll be able to trigger actions from modules, so no matter if is working as isolated or module.&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%2Flix2p1qz194wkoc4axy2.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%2Flix2p1qz194wkoc4axy2.png" alt="Fig. 3: shared store when component is injected" width="697" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trigger an action from module
&lt;/h2&gt;

&lt;p&gt;To trigger an action on Module, we can do it exactly as we do it on any react-redux app. There are only few things to keep in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When we export a component to work as a module, we are exporting the component and not the reducers, so if we want to have same reducers on base and module, we need to inject it. 
As que saw in the last image, the exported component will have different provider depending how we work with it.&lt;/li&gt;
&lt;li&gt;If we need a reducer and we don’t want to inject it because we have it from the beginning on the app, we have to search a way to export the reducer from module an include it on base to add it to the store when necessary, avoiding to have the same duplicated reducer on base and module.&lt;/li&gt;
&lt;li&gt;We don’t need to change anything to trigger an action, but the available reducers will depend on where the component is mounted. If it's working isolated, we’ll need to mock or recreate the reducers that we need from base. If is working as module on base, we’ll need to add the reducers that we have on sub-app.&lt;/li&gt;
&lt;li&gt;For testing you'll have to import or mock as in the point 3&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Inject reducer from module to base
&lt;/h2&gt;

&lt;p&gt;If you're working on big react app with redux, you probably faced the reducer injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const injectAsyncReducer = (store, name, asyncReducer) =&amp;gt; {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably wrote a function similar to this, and that code will work perfectly for both of the cases that we're managing here: working isolated and as a module. Why? because in the two cases we'll have a redux provider, the only difference will be that each case will inject the reducer in a different store, but you'll have nothing to deal with. &lt;/p&gt;

&lt;p&gt;NOTE: Yes, you can inject redux-sagas as well, if you're curious about I drop this &lt;a href="https://scite.ai/blog/2021-11-10_Redux-store-splitting-bundle-optimization" rel="noopener noreferrer"&gt;line here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;If you were worried about split your monolith in federated modules due to redux, there's nothing to be afraid. The most complicated part probably will be the Webpack configuration and not the redux. &lt;/p&gt;

&lt;p&gt;Thank to &lt;a href="https://www.instagram.com/english.with.yama/" rel="noopener noreferrer"&gt;english.with.yama@gmail.com&lt;/a&gt; for proofreading the article.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>React 18 setTransition</title>
      <dc:creator>Pascal Fong Kye</dc:creator>
      <pubDate>Thu, 27 Jan 2022 09:06:47 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/react-18-settransition-4oop</link>
      <guid>https://forem.com/manomano-tech-team/react-18-settransition-4oop</guid>
      <description>&lt;h2&gt;
  
  
  startTransition
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://reactjs.org/blog/2021/12/17/react-conf-2021-recap.html"&gt;React 18&lt;/a&gt; introduces a new concurrent feature called &lt;a href="https://github.com/reactwg/react-18/discussions/41"&gt;startTransition&lt;/a&gt; which aims to solve problems we tried to solve using &lt;a href="https://css-tricks.com/debouncing-throttling-explained-examples/"&gt;debounce and throttle&lt;/a&gt;, and much more.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/reactwg/react-18/discussions/65"&gt;thread&lt;/a&gt; gives a deep dive example of how it solves the problem of UI responsiveness when there is expensive computation taking place.&lt;/p&gt;

&lt;p&gt;Before React 18, we tend to use debounce (or throttle depending on the situation) to schedule this compute intensive task at a given time &lt;em&gt;T&lt;/em&gt; so that the UI feels more responsive to other events. But every time the task will start after &lt;em&gt;T&lt;/em&gt; has elapsed, even if it takes less time than &lt;em&gt;T&lt;/em&gt;. And the task will still occur as a whole chunk, blocking the thread during this time. &lt;/p&gt;

&lt;p&gt;This is where startTransition API brings a new approach to deal with these problems.&lt;br&gt;
It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;split tasks into small chunks and be smart enough to let the browser process incoming events (mouse events for example) -&amp;gt; &lt;strong&gt;yielding&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;manage an urgent update in priority skipping pending ones -&amp;gt; &lt;strong&gt;interrupting&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;update using new state by disposing of unfinished task with stale state -&amp;gt; &lt;strong&gt;skipping old results&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check my &lt;a href="https://nextjs-blog-pfongkye.vercel.app/"&gt;personal blog&lt;/a&gt; for more articles.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to add custom types to a javascript library</title>
      <dc:creator>Danielo Artola</dc:creator>
      <pubDate>Mon, 06 Dec 2021 16:12:52 +0000</pubDate>
      <link>https://forem.com/manomano-tech-team/how-to-add-custom-types-to-a-javascript-library-2dag</link>
      <guid>https://forem.com/manomano-tech-team/how-to-add-custom-types-to-a-javascript-library-2dag</guid>
      <description>&lt;p&gt;Few weeks ago, I started contributing to an open source library called &lt;a href="https://github.com/teafuljs/teaful" rel="noopener noreferrer"&gt;Teaful&lt;/a&gt;, a Tiny, EAsy, and powerFUL for React state management, with ambitious roadmap. Now &lt;code&gt;Teaful&lt;/code&gt; reached more than &lt;strong&gt;500 GitHub ⭐️ Stars&lt;/strong&gt;, the library and his community are growing fast.&lt;/p&gt;

&lt;p&gt;That means issues and pull requests are growing as well, and soon we realized that we need to improve dev-experience and provide &lt;a href="https://dev.to/aralroca/teaful-devtools-released-37lp"&gt;tools&lt;/a&gt; for that reason.&lt;/p&gt;

&lt;p&gt;Bear this in mind, implement custom types to allow all the benefits from &lt;code&gt;TypeScript&lt;/code&gt; at &lt;code&gt;Teaful&lt;/code&gt; is a big step on that way.&lt;/p&gt;

&lt;p&gt;(Yes, I know, migrate a library to pure ts probably is a better solution, and it's on our &lt;a href="https://github.com/teafuljs/teaful#roadmap-" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; before 1.0.0)&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%2Frxj91tgnh9as4jn3ualx.gif" 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%2Frxj91tgnh9as4jn3ualx.gif" alt="Autocomplete code gif" width="813" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Index
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Avoiding auto generated types&lt;/li&gt;
&lt;li&gt;Where to place custom types&lt;/li&gt;
&lt;li&gt;Create custom types&lt;/li&gt;
&lt;li&gt;Conclusions&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Avoiding auto generated types
&lt;/h2&gt;

&lt;p&gt;In our case, an auto-generated custom type full of &lt;code&gt;any&lt;/code&gt; was useless. So, we started implementing custom types.&lt;/p&gt;

&lt;p&gt;We're using &lt;code&gt;microbundle&lt;/code&gt;, they provide a flag to avoid auto-generate types, &lt;code&gt;--no-generateTypes&lt;/code&gt;. Microbundle, according to docs, generally respect your TypeScript config at &lt;code&gt;tsconfig.json&lt;/code&gt; (you can read more about &lt;a href="https://github.com/developit/microbundle#using-with-typescript" rel="noopener noreferrer"&gt;here&lt;/a&gt;), but at this moment we don't need a specific configuration for &lt;code&gt;TypeScript&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then we can inform on &lt;code&gt;package.json&lt;/code&gt; where are our custom types with &lt;code&gt;"types": "folder/index.d.ts"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to place custom types
&lt;/h2&gt;

&lt;p&gt;Create a file with extension &lt;code&gt;.d.ts&lt;/code&gt; , generally you'll put this file on &lt;code&gt;dist&lt;/code&gt; folder. Now here you can add your custom types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create custom types
&lt;/h2&gt;

&lt;p&gt;Here  I'm going to explain how we created custom types specifics for &lt;code&gt;Teaful&lt;/code&gt; and why some decisions were taken, if you're reading this to know how to add custom types to your js library and already know about &lt;code&gt;TypeScript&lt;/code&gt;, feel free to skip this section.&lt;/p&gt;

&lt;h3&gt;
  
  
  InitialStore
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;store&lt;/code&gt; is where &lt;code&gt;Teaful&lt;/code&gt; saves data, is a key-value object (you can have more than one store). Easy to type:&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;type&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far so good, nothing strange here. We want to store anything, and all keys will be string.&lt;/p&gt;

&lt;p&gt;Then things become more complicated. In this article only things about creating types will be explained, so if you want to know more about how to implement &lt;code&gt;Teaful&lt;/code&gt; I strongly recommend visit the &lt;a href="https://github.com/teafuljs/teaful#teaful" rel="noopener noreferrer"&gt;README at github&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hook Return
&lt;/h3&gt;

&lt;p&gt;To create a new value on store is pretty similar to &lt;code&gt;useState&lt;/code&gt; from &lt;code&gt;React&lt;/code&gt;. Let's see an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&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="nx"&gt;setUsername&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy right? Ok, so what have we here? &lt;code&gt;useStore&lt;/code&gt; returns an array of two elements (Yes! Like useState!), the element in the store and the function to update it. &lt;/p&gt;

&lt;p&gt;The type we need:&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;type&lt;/span&gt; &lt;span class="nx"&gt;HookReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're not familiar with TS, this could look a little cryptic. We're creating a new type called &lt;code&gt;HookReturn&lt;/code&gt; which gets a generic type we called '&lt;code&gt;T&lt;/code&gt;' (from Type, but you can use any name). &lt;/p&gt;

&lt;p&gt;This type is a tuple(a data structure that is an ordered list of elements with a fixed length, because we aren't going to add more elements for the return of our &lt;code&gt;useStore&lt;/code&gt;), where first element is &lt;code&gt;T&lt;/code&gt;, because we want to return a value with specific type that we don't know at the moment of creating the type, but we want to ensure, for example, that the setter function (the second element on this tuple) will get the same type we are using for the first element as param.&lt;/p&gt;

&lt;p&gt;Then, let's pay attention on the second element of our tuple.&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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="k"&gt;void&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, our type is a function that returns nothing ( &lt;code&gt;() =&amp;gt; void&lt;/code&gt;), but accepts one param (&lt;code&gt;value: T | ((value: T) =&amp;gt; T | undefined | null)&lt;/code&gt;), and this param could be a value of type &lt;code&gt;T&lt;/code&gt;, or a function that get a value of type &lt;code&gt;T&lt;/code&gt; and returns &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt; or a value of type &lt;code&gt;T&lt;/code&gt; (&lt;code&gt;(value: T) =&amp;gt; T | undefined | null&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;What this means? what are we allowing here with this type? Ok, let's imagine a counter:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;//allowed by T&lt;/span&gt;
&lt;span class="nf"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&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="c1"&gt;//allowed by  ((value: T) =&amp;gt; T | undefined | null)&lt;/span&gt;
&lt;span class="nf"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;counter&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="nf"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, Teaful accepts a function as param on the setter function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook type
&lt;/h3&gt;

&lt;p&gt;When you create/call a new property with &lt;a href="https://github.com/teafuljs/teaful#usestore-hook" rel="noopener noreferrer"&gt;useStore&lt;/a&gt;, you call &lt;code&gt;useStore.[newProperty]()&lt;/code&gt;. This accepts two optional params, first for &lt;code&gt;initialValue&lt;/code&gt;, and the second one is for &lt;code&gt;updateValue&lt;/code&gt; (a function to update the store property indicated with the &lt;code&gt;proxy&lt;/code&gt;). The hook looks easy to create 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="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Hook&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;onAfterUpdate&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;afterCallbackType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;HookReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both optional, but the second one is a specific function. Type &lt;code&gt;onAfterUpdate&lt;/code&gt;, is a function with two params: &lt;code&gt;store&lt;/code&gt; before and after the changes, both will be same type, extending our &lt;code&gt;initialStore&lt;/code&gt; type.&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;type&lt;/span&gt; &lt;span class="nx"&gt;afterCallbackType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;prevStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, our type &lt;code&gt;Hook&lt;/code&gt; will return a tuple &lt;code&gt;[property,setter]&lt;/code&gt;, so indeed, we're going to return our custom type &lt;code&gt;HookReturn&lt;/code&gt; with our generic type. If we create a number, have sense to take care about number type in all places, for the initial value, the returned tuple... etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hoc type
&lt;/h3&gt;

&lt;p&gt;Teaful allows to use it as Hoc (as connect on Redux, code explain it by itself):&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStore&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&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="c1"&gt;// Similar to useStore()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CounterWithStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HOC &lt;a href="https://github.com/teafuljs/teaful#withstore-hoc" rel="noopener noreferrer"&gt;&lt;code&gt;withStore&lt;/code&gt;&lt;/a&gt; wraps a &lt;code&gt;Component&lt;/code&gt; and returns the component with a prop called store. A second parameter for initial value is allowed, and a third one for &lt;code&gt;onAfterUpdate&lt;/code&gt; callback.&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;type&lt;/span&gt; &lt;span class="nx"&gt;HocFunc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ComponentClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ComponentClass&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="nx"&gt;onAfterUpdate&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;afterCallbackType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need two generic types, one for initial value and &lt;code&gt;onAfterUpdate&lt;/code&gt; (both will use same generic, but &lt;code&gt;onAfterUpdate&lt;/code&gt; will have a specific type, explained later) and the other one for &lt;code&gt;React&lt;/code&gt; component to wrap that would be the same for the return, because we want the same component but with a new prop called store.&lt;/p&gt;

&lt;p&gt;Look at the &lt;code&gt;R&lt;/code&gt; type, is extending &lt;code&gt;React.ComponentClass&lt;/code&gt; (type provided by &lt;code&gt;React&lt;/code&gt;). This means that we are taking profit from that type and including it in our generic type called &lt;code&gt;R&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Why extending component class only and not functional component? &lt;/p&gt;

&lt;p&gt;Well, we didn't found a single situation when we wanted to wrap any component that doesn't extend Class with a HOC to get the store.&lt;/p&gt;

&lt;p&gt;Ok, third type: &lt;code&gt;onAfterUpdate&lt;/code&gt;.  Here we need a function with two params store before and after the changes, both will be same type, extending our &lt;code&gt;initialStore&lt;/code&gt; type. Same as first hook, we reuse same type for all callbacks params&lt;/p&gt;

&lt;p&gt;Now we only have to export the a type to use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Hoc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HookReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HookDry type
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Teaful&lt;/code&gt; provides a helper called &lt;a href="https://github.com/teafuljs/teaful#getstore-helper" rel="noopener noreferrer"&gt;&lt;code&gt;getStore&lt;/code&gt;&lt;/a&gt;, like useStore but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not make a subscription. So it is no longer a hook and you can use it as a helper wherever you want.&lt;/li&gt;
&lt;li&gt;It's not possible to register events that are executed after a change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means we don't want same as &lt;code&gt;useStore&lt;/code&gt;type, we return the same but we want to ensure we don't accept a second param as callback. Let's create another one:&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;type&lt;/span&gt; &lt;span class="nx"&gt;HookDry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;HookReturn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;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 return is clear, same as Hook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's type useStore, getStore and withStore
&lt;/h3&gt;

&lt;p&gt;Ok, now we have almost all the work done. A custom type is needed for each tool, &lt;code&gt;useStore&lt;/code&gt;, &lt;code&gt;getStore&lt;/code&gt;and &lt;code&gt;withStore&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;type&lt;/span&gt; &lt;span class="nx"&gt;getStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;useStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;HookDry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HookDry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;useStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;useStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;Hook&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hook&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;withStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;withStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;HocFunc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HocFunc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://www.typescriptlang.org/docs/handbook/2/keyof-types.html" rel="noopener noreferrer"&gt;&lt;code&gt;keyOf&lt;/code&gt;&lt;/a&gt; type operator ensures that our &lt;code&gt;property&lt;/code&gt; will exist on &lt;code&gt;store&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The ternary here looks weird if you're not familiar with &lt;code&gt;Typescript&lt;/code&gt;, is used for &lt;a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html" rel="noopener noreferrer"&gt;conditional-types&lt;/a&gt;. The logic shared in three types is, get a generic type (&lt;code&gt;S&lt;/code&gt;, that extends our &lt;code&gt;initialStoreType&lt;/code&gt;), then get a &lt;code&gt;key&lt;/code&gt;that must be on &lt;code&gt;S&lt;/code&gt; (the property should exists on our store). &lt;/p&gt;

&lt;p&gt;Finally, this &lt;code&gt;withStoreType&amp;lt;S[key]&amp;gt; &amp;amp; HocFunc&amp;lt;S&amp;gt;&lt;/code&gt; is a &lt;a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types" rel="noopener noreferrer"&gt;Intersection type&lt;/a&gt;. According to TypeScript documentation "An intersection type combines multiple types into one". So if &lt;code&gt;S[key]&lt;/code&gt; extends &lt;code&gt;initialStore&lt;/code&gt;, we set the intersection type, if not, the hook/hoc type only.&lt;/p&gt;

&lt;h3&gt;
  
  
  createStore
&lt;/h3&gt;

&lt;p&gt;Last, the function to export from &lt;code&gt;Teaful&lt;/code&gt;, the masterpiece:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createStore&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;initialStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;afterCallback&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;afterCallbackType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HookDry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;getStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hook&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;useStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;withStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HocFunc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;withStoreType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;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;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;That's definitely not everything, but there are few steps that you'll face:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check how to stop auto-generated types, check if types are generated by the bundler like our case, by &lt;code&gt;tsconfig.json&lt;/code&gt; or whatever.&lt;/li&gt;
&lt;li&gt;Create a custom types on a &lt;code&gt;d.ts&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Indicate to &lt;code&gt;package.json&lt;/code&gt; the place of that file with property &lt;code&gt;"types"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Adding custom types to a javascript library could be difficult at the beginning, but will improve the dev-experience from your users. &lt;/p&gt;

&lt;p&gt;And most important, this could be a great opportunity to learn and improve your skills, to start networking with the community or a good way to help other devs.&lt;/p&gt;

&lt;p&gt;I hope it was helpful to you, have a super nice day!&lt;/p&gt;

&lt;p&gt;Thank to &lt;a href="https://www.instagram.com/english.with.yama/" rel="noopener noreferrer"&gt;english.with.yama@gmail.com&lt;/a&gt; for proofreading the article.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>teaful</category>
      <category>react</category>
    </item>
  </channel>
</rss>
