<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roger Viñas Alcon</title>
    <description>The latest articles on DEV Community by Roger Viñas Alcon (@rogervinas).</description>
    <link>https://dev.to/rogervinas</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F402420%2F2993c9a6-49a0-4c32-a150-fd40c4b57dde.jpeg</url>
      <title>DEV Community: Roger Viñas Alcon</title>
      <link>https://dev.to/rogervinas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rogervinas"/>
    <language>en</language>
    <item>
      <title>Strands Agents + Langfuse Evaluations</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 02 Jul 2026 13:18:29 +0000</pubDate>
      <link>https://dev.to/rogervinas/strands-agents-langfuse-evaluations-25hd</link>
      <guid>https://dev.to/rogervinas/strands-agents-langfuse-evaluations-25hd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Before diving in, check out &lt;strong&gt;&lt;a href="https://rogervinas.github.io/strands-agents-langfuse-evaluations/" rel="noopener noreferrer"&gt;AI was supposed to take my job — instead it gave me a new one: Evaluations&lt;/a&gt;&lt;/strong&gt;, a presentation that walks through this PoC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations" rel="noopener noreferrer"&gt;See source code at Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this project we will build a Python banking assistant agent using &lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; and make it observable and continuously evaluated using &lt;a href="https://langfuse.com" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt; — step by step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt; is a lightweight Python SDK for building LLM-powered agents with tool use and session memory, open-sourced by AWS in May 2025. It is Python-native — which pairs well with the Langfuse Python SDK — and new enough to be worth exploring. Any other Python agent framework would work just as well for this PoC.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;classic applications&lt;/strong&gt;, &lt;strong&gt;quality is enforced through unit tests, integration tests, and static analysis&lt;/strong&gt; — every function has a defined contract and a deterministic output you can assert on. In production, metrics (error rates, latency, memory) surface failures reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI applications break both of these&lt;/strong&gt;. The same input may yield different outputs on each run — wording changes, tools get called in a different order, edge cases surface unpredictably. And in production, a request can return 200 OK in 300ms with a confident, completely wrong answer — classic metrics won't catch it. &lt;strong&gt;You need something more!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;traces&lt;/strong&gt; and &lt;strong&gt;evaluations&lt;/strong&gt; come in — supported by a growing number of platforms: &lt;a href="https://langfuse.com/docs" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt;, &lt;a href="https://docs.arize.com/phoenix" rel="noopener noreferrer"&gt;Arize Phoenix&lt;/a&gt;, &lt;a href="https://mlflow.org/docs/latest/llms/tracing/index.html" rel="noopener noreferrer"&gt;MLflow&lt;/a&gt;, &lt;a href="https://docs.smith.langchain.com/" rel="noopener noreferrer"&gt;LangSmith&lt;/a&gt;, &lt;a href="https://weave-docs.wandb.ai/" rel="noopener noreferrer"&gt;W&amp;amp;B Weave&lt;/a&gt;, &lt;a href="https://docs.datadoghq.com/llm_observability/evaluations/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/evaluations.html" rel="noopener noreferrer"&gt;AWS AgentCore&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/concepts/evaluation-approach-gen-ai" rel="noopener noreferrer"&gt;Azure AI Foundry&lt;/a&gt;, &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/evaluation-overview" rel="noopener noreferrer"&gt;Google Vertex AI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This PoC uses &lt;strong&gt;Langfuse&lt;/strong&gt; because it is open-source and is self-hostable with a single &lt;code&gt;docker compose up&lt;/code&gt;, providing these features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tracing&lt;/strong&gt; — recording a structured tree of every LLM call, tool call, and sub-agent step: inputs, outputs, latency, cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluations&lt;/strong&gt; — running scored assessments of agent outputs:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offline&lt;/strong&gt; — run against a fixed, curated dataset before or after a change; re-runnable as a CI quality gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Online&lt;/strong&gt; — async, triggered by live traces; catch issues that didn't appear in your fixed dataset.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External Evaluations&lt;/strong&gt; — attaching scores to live traces programmatically from your own code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annotation Queues&lt;/strong&gt; — routing traces to human reviewers via explicit programmatic calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Management&lt;/strong&gt; — versioning prompt templates and pulling them at runtime via SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app under evaluation is the &lt;strong&gt;banking sentinel&lt;/strong&gt; — a customer support agent for ROGERVINAS bank built with &lt;a href="https://strandsagents.com" rel="noopener noreferrer"&gt;Strands Agents&lt;/a&gt;: 3 mock accounts with 5 transactions each, and tools to freeze/unfreeze cards, look up transactions, and open or track disputes.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpsnd97gkq7k9ntb7636.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcpsnd97gkq7k9ntb7636.png" width="799" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready?&lt;/strong&gt; Get the agent and Langfuse running (Configuration → Run), open the chat UI and play with it, then go through the Implementation steps to see how each piece works.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Run&lt;/li&gt;
&lt;li&gt;
Implementation

&lt;ul&gt;
&lt;li&gt;Step 1: The Banking Agent&lt;/li&gt;
&lt;li&gt;Step 2: Tracing&lt;/li&gt;
&lt;li&gt;Step 3: Strands Native Evaluations&lt;/li&gt;
&lt;li&gt;Step 4: Experiments&lt;/li&gt;
&lt;li&gt;Step 5: Online Evaluations&lt;/li&gt;
&lt;li&gt;Step 6: External Evaluations&lt;/li&gt;
&lt;li&gt;Step 7: Annotation Queues&lt;/li&gt;
&lt;li&gt;Step 8: Prompt Management&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python (version pinned in &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/.python-version" rel="noopener noreferrer"&gt;&lt;code&gt;.python-version&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docker + Docker Compose&lt;/li&gt;
&lt;li&gt;A model provider (see below)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--extra&lt;/span&gt; dev &lt;span class="nt"&gt;--extra&lt;/span&gt; evals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dev&lt;/code&gt; — dependencies for unit tests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evals&lt;/code&gt; — dependencies for evaluations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;.env&lt;/code&gt; to set your model provider and Langfuse credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model providers
&lt;/h3&gt;

&lt;p&gt;Set &lt;code&gt;MODEL_PROVIDER&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;&lt;code&gt;MODEL_PROVIDER&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Requirements&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ollama (default)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ollama&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ollama serve&lt;/code&gt; + &lt;code&gt;ollama pull llama3.1:8b&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bedrock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AWS credentials configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GOOGLE_API_KEY&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Langfuse
&lt;/h3&gt;

&lt;p&gt;We use the &lt;a href="https://langfuse.com/self-hosting/deployment/docker-compose" rel="noopener noreferrer"&gt;self-hosted with Docker Compose&lt;/a&gt; deployment. This PoC's &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/docker-compose-langfuse.yml" rel="noopener noreferrer"&gt;&lt;code&gt;docker-compose-langfuse.yml&lt;/code&gt;&lt;/a&gt; is a copy of the &lt;a href="https://github.com/langfuse/langfuse/blob/main/docker-compose.yml" rel="noopener noreferrer"&gt;official docker-compose&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-langfuse.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the Langfuse UI at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → project &lt;code&gt;banking-sentinel&lt;/code&gt;, with these pre-provisioned credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;Email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt;      admin@local.dev&lt;/span&gt;
&lt;span class="nt"&gt;Password&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt;   password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-langfuse.yml down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stop and delete all data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-langfuse.yml down &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Run
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run uvicorn banking_sentinel.api:app &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; and try it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the textboxes to simulate a different &lt;strong&gt;User ID&lt;/strong&gt;, &lt;strong&gt;Account ID&lt;/strong&gt;, &lt;strong&gt;Tier&lt;/strong&gt;, and &lt;strong&gt;Session ID&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Chat with the agent — for example, say &lt;em&gt;"Help, I do not have Netflix!"&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Each answer may include &lt;strong&gt;suggested actions&lt;/strong&gt; you can click directly, plus 👍 / 👎 feedback buttons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent traces are sent to Langfuse automatically via OpenTelemetry, as the &lt;code&gt;LANGFUSE_*&lt;/code&gt; and &lt;code&gt;OTEL_*&lt;/code&gt; env vars are set in &lt;code&gt;.env&lt;/code&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fucazg25oexcprbr0fwg8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fucazg25oexcprbr0fwg8.png" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;With the agent and Langfuse running, let's walk through how each piece works, step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: The Banking Agent
&lt;/h3&gt;

&lt;p&gt;The domain: a banking customer support agent — the &lt;strong&gt;Sentinel&lt;/strong&gt; — for ROGERVINAS bank. Three mock accounts (&lt;code&gt;ACC-1001&lt;/code&gt;, &lt;code&gt;ACC-1002&lt;/code&gt;, &lt;code&gt;ACC-1003&lt;/code&gt;), five transactions each, and seven tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;freeze_card&lt;/code&gt; — Freeze a card&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unfreeze_card&lt;/code&gt; — Unfreeze a card&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is_card_frozen&lt;/code&gt; — Check freeze status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_transactions&lt;/code&gt; — List transactions between two dates&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;open_dispute&lt;/code&gt; — Open a dispute on a transaction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_dispute_status&lt;/code&gt; — Check dispute status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_disputes&lt;/code&gt; — List all disputes for an account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementation is intentionally minimal — a single process, file-based session storage, no RAG (the knowledge base is short enough to inline directly in the system prompt), no MCP, no external service calls. The goal here is to keep the agent simple so the focus stays on observability and evaluations. The full implementation lives under &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/src/banking_sentinel" rel="noopener noreferrer"&gt;&lt;code&gt;src/banking_sentinel&lt;/code&gt;&lt;/a&gt;; if you are new to Strands, follow the &lt;a href="https://strandsagents.com/latest/documentation/docs/get-started/quick-start/" rel="noopener noreferrer"&gt;Strands Agents tutorial&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;In production, the agent would run behind a load balancer with multiple replicas, connect to real external services via MCP or direct API calls, and swap the local file-based session storage for a persistent store — see the &lt;a href="https://strandsagents.com/latest/documentation/docs/user-guide/concepts/sessions/" rel="noopener noreferrer"&gt;Strands session management docs&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Tracing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Traces are not standardised&lt;/strong&gt; — what you get depends entirely on the framework and instrumentation. The GenAI OTel semantic conventions are still experimental, so SDKs and platforms implement them inconsistently — always verify your traces in the UI before relying on them.&lt;/p&gt;

&lt;p&gt;In this PoC, for example, the Strands &lt;code&gt;[otel]&lt;/code&gt; extra emits spans but does not set trace-level &lt;code&gt;input&lt;/code&gt;/&lt;code&gt;output&lt;/code&gt; on the root span — and we need those in later steps. So &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/src/banking_sentinel/api.py" rel="noopener noreferrer"&gt;&lt;code&gt;api.py&lt;/code&gt;&lt;/a&gt; wraps the entire &lt;code&gt;/chat&lt;/code&gt; request in &lt;code&gt;langfuse.start_as_current_observation()&lt;/code&gt;, which sets &lt;code&gt;input&lt;/code&gt;/&lt;code&gt;output&lt;/code&gt; on the root span while capturing the Strands OTel spans as children:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;langfuse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banking-sentinel-chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... run the agent ...
&lt;/span&gt;    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt_obj&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 the following span hierarchy in Langfuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;banking-sentinel-chat  ← Langfuse-native root span (input/output/user_id)
  └── Strands OTel spans  ← captured automatically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Traces&lt;/strong&gt; to see it:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0aish1o1nbqg0xj7djwf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0aish1o1nbqg0xj7djwf.png" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Strands Native Evaluations
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://strandsagents.com/latest/documentation/docs/deploy/evaluation/" rel="noopener noreferrer"&gt;Strands Evals SDK&lt;/a&gt; provides &lt;code&gt;Case&lt;/code&gt;, &lt;code&gt;Experiment&lt;/code&gt;, and &lt;code&gt;OutputEvaluator&lt;/code&gt;. No Langfuse required — runs fully offline and produces a local report. The full code for this step lives in &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/evals/strands/run_evaluations.py" rel="noopener noreferrer"&gt;&lt;code&gt;evals/strands/run_evaluations.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;Case&lt;/code&gt; bundles an input and expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unauthorized-netflix-charge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-1001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accountId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACC-1001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accountTier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t have Netflix but I see a charge on my account&lt;/span&gt;&lt;span class="sh"&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;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggestedActions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FREEZE_CARD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The AI agent found a Netflix charge of 9.99 and offered the user to open a dispute&lt;/span&gt;&lt;span class="sh"&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;# ...
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two evaluators score each result — one deterministic, one LLM-as-judge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CorrectnessEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OutputEvaluator&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClaimEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OutputEvaluator&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="n"&gt;correctness_evaluator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CorrectnessEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rubric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Score 1.0 if the actual output&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s suggested_actions contains all actions listed in expected_output&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s suggestedActions. Score 0.0 if any expected action is missing.&lt;/span&gt;&lt;span class="sh"&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;claim_evaluator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ClaimEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rubric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Score 1.0 if the actual output&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s answer matches the claim in expected_output. Score 0.0 if the answer does not match the claim.&lt;/span&gt;&lt;span class="sh"&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Strands Evals can evaluate any Python callable — there is no coupling to the Strands agent itself. If you prefer a different evaluation framework, alternatives include &lt;a href="https://deepeval.com" rel="noopener noreferrer"&gt;DeepEval&lt;/a&gt;, &lt;a href="https://ragas.io" rel="noopener noreferrer"&gt;Ragas&lt;/a&gt; (RAG-focused), &lt;a href="https://www.braintrustdata.com/docs/autoevals/overview" rel="noopener noreferrer"&gt;Braintrust autoevals&lt;/a&gt;, or plain &lt;a href="https://docs.pytest.org" rel="noopener noreferrer"&gt;pytest&lt;/a&gt; with custom assertions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are two ways to run the task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Embedded&lt;/strong&gt; — the agent is instantiated in-process; external services are mocked, and you can inspect internal state (white-box). Fast, no server needed, ideal for CI. Use this when you want fast, isolated, reproducible runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API&lt;/strong&gt; — the task hits a running server with real external services (black-box). Use this to validate against a live deployment or when mocking is not practical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run embedded — no server needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.strands.run_evaluations embedded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run against a running server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.strands.run_evaluations api &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Report should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;============================================================
Evaluator: CorrectnessEvaluator
Overall score: 1.00
  ✅ unauthorized-netflix-charge: score=1.00 — suggested_actions contains the expected 'FREEZE_CARD'.
  ✅ expired-dispute-window: score=1.00 — suggested_actions contains the expected 'FREEZE_CARD'.

============================================================
Evaluator: ClaimEvaluator
Overall score: 1.00
  ✅ unauthorized-netflix-charge: score=1.00 — identifies the Netflix charge and offers to open a dispute.
  ✅ expired-dispute-window: score=1.00 — explains the 14-day dispute window has expired.

✅ Evaluation PASSED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 4: Experiments
&lt;/h3&gt;

&lt;p&gt;A Langfuse &lt;strong&gt;experiment&lt;/strong&gt; is an offline evaluation run: your agent is executed against a curated dataset, each output is scored automatically, and the results are stored so you can compare across code versions, prompt changes, and model upgrades over time.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;dataset&lt;/strong&gt; is a versioned collection of test cases — each item has an &lt;code&gt;input&lt;/code&gt;, an &lt;code&gt;expected_output&lt;/code&gt;, and optional &lt;code&gt;metadata&lt;/code&gt;. Each experiment execution is named and stored against that dataset, with evaluator scores recorded per item.&lt;/p&gt;

&lt;p&gt;Where Step 3: Strands Native Evaluations produces a local pass/fail report with no history, Langfuse Experiments add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Comparison across runs&lt;/strong&gt; — see how scores change between code versions, prompt changes, or model upgrades side by side in the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent results&lt;/strong&gt; — every run is stored; you can go back and audit any historical experiment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both Step 3: Strands Native Evaluations and Step 4: Experiments act as a CI quality gate — the script exits non-zero if scores drop below threshold.&lt;/p&gt;

&lt;p&gt;This step has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the dataset&lt;/li&gt;
&lt;li&gt;Implement the experiment&lt;/li&gt;
&lt;li&gt;Run the experiment&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Create the dataset
&lt;/h4&gt;

&lt;p&gt;Each item has an &lt;code&gt;input&lt;/code&gt;, an &lt;code&gt;expected_output&lt;/code&gt;, and optional &lt;code&gt;metadata&lt;/code&gt;. Datasets can be created in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Via UI&lt;/strong&gt;: go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Datasets&lt;/strong&gt; → &lt;strong&gt;New dataset&lt;/strong&gt;, then add items manually&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatically&lt;/strong&gt; (idempotent — safe to run repeatedly), see &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/evals/langfuse/create_dataset.py" rel="noopener noreferrer"&gt;&lt;code&gt;evals/langfuse/create_dataset.py&lt;/code&gt;&lt;/a&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.langfuse.create_dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Datasets&lt;/strong&gt; → &lt;strong&gt;Items&lt;/strong&gt; to see the dataset items:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5702ade1ocfsjm5xq2b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5702ade1ocfsjm5xq2b.png" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Implement the experiment
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Evaluators&lt;/strong&gt; are plain Python callables — there are no built-in evaluators in the SDK and no base class to inherit from. Any function that matches this signature works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langfuse.experiment&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Evaluation&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_evaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;input&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="c1"&gt;# the dataset item's input
&lt;/span&gt;    &lt;span class="n"&gt;output&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="c1"&gt;# what the task returned
&lt;/span&gt;    &lt;span class="n"&gt;expected_output&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="c1"&gt;# the dataset item's expected_output
&lt;/span&gt;    &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Evaluation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# or List[Evaluation] for multiple metrics at once
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Evaluation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# metric name shown in the dashboard
&lt;/span&gt;        &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# int | float | str | bool
&lt;/span&gt;        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;optional&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# shown alongside the score
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We implement the same two evaluators as in Step 3: Strands Native Evaluations — one deterministic (&lt;strong&gt;correctness&lt;/strong&gt;), one LLM-as-judge (&lt;strong&gt;claim&lt;/strong&gt;). The LLM-as-judge runs &lt;strong&gt;locally&lt;/strong&gt; using whatever &lt;code&gt;MODEL_PROVIDER&lt;/code&gt; you have configured (Ollama, Bedrock, Gemini) — it is not Langfuse's evaluation infrastructure, just your own model called from Python (see &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/evals/langfuse/run_experiment.py" rel="noopener noreferrer"&gt;&lt;code&gt;evals/langfuse/run_experiment.py&lt;/code&gt;&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;correctness_evaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Evaluation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Deterministic: checks if all expected suggested actions are present.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggestedActions&lt;/span&gt;&lt;span class="sh"&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;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggested_actions&lt;/span&gt;&lt;span class="sh"&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;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&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;expected&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Evaluation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;correctness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;claim_evaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Evaluation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;LLM-as-judge: runs locally with your configured MODEL_PROVIDER.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;judge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Does the following answer match the claim? Reply with YES or NO only.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Claim: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;claim&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Evaluation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim_match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;langfuse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_experiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banking-sentinel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embedded_task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;evaluators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;correctness_evaluator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claim_evaluator&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;max_concurrency&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Run the experiment
&lt;/h4&gt;

&lt;p&gt;Run embedded — no server needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.langfuse.run_experiment embedded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run against a running server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.langfuse.run_experiment api &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Datasets&lt;/strong&gt; → &lt;strong&gt;Experiments&lt;/strong&gt; to see the experiment runs:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9oqzx726edtd8chms96.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc9oqzx726edtd8chms96.png" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5: Online Evaluations
&lt;/h3&gt;

&lt;p&gt;Langfuse can automatically score live traces as they arrive — no code changes needed. In this PoC all chat traces are tagged &lt;code&gt;banking-sentinel&lt;/code&gt;, and the root span of each is named &lt;code&gt;banking-sentinel-chat&lt;/code&gt;, making them easy to target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 — Add LLM Connection:&lt;/strong&gt;&lt;br&gt;
Go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Settings → LLM Connections&lt;/strong&gt; → add your model provider API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 — Set default evaluation model:&lt;/strong&gt;&lt;br&gt;
Go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Evaluators&lt;/strong&gt; → set the &lt;strong&gt;Default Evaluation Model&lt;/strong&gt; to the connection you just added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 — Create the evaluator:&lt;/strong&gt;&lt;br&gt;
Go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Evaluators&lt;/strong&gt; → &lt;strong&gt;Create Evaluator&lt;/strong&gt;. Two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create from scratch&lt;/strong&gt; (custom prompt) — your own domain-aware prompt. This is what we use for the PoC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use existing&lt;/strong&gt; (built-in templates, e.g. &lt;strong&gt;Helpfulness&lt;/strong&gt;, &lt;strong&gt;Hallucination&lt;/strong&gt;) — often too generic for a specific domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fill in the custom evaluator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt; — e.g. &lt;code&gt;banking-sentinel-helpfulness&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt; — leave &lt;strong&gt;Use default evaluation model&lt;/strong&gt; checked (the model you set in step 2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation prompt&lt;/strong&gt; — reference the trace content with &lt;code&gt;{{input}}&lt;/code&gt; and &lt;code&gt;{{output}}&lt;/code&gt; (see below)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score type&lt;/strong&gt; — &lt;code&gt;numeric&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, or &lt;code&gt;categorical&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score reasoning prompt&lt;/strong&gt; and &lt;strong&gt;Score range prompt&lt;/strong&gt; — leave at their defaults or customize at will&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Evaluation prompt:&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 evaluating a banking customer-support assistant for ROGERVINAS bank.
Customers often report problems informally, without using precise banking
terms. Evaluate how helpful the assistant's reply is: does it identify the
relevant transaction, explain the applicable policy (e.g. the dispute window),
and offer concrete next steps (freeze card, open dispute)?

Customer message:
{{input}}

Assistant reply:
{{output}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4 — Configure the rule:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set target to &lt;code&gt;Observations&lt;/code&gt;, filter by &lt;code&gt;Type = GENERATION&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ensure &lt;strong&gt;Run on live incoming observations&lt;/strong&gt; is checked (it is the default)&lt;/li&gt;
&lt;li&gt;Add filter: &lt;code&gt;Tags&lt;/code&gt; → &lt;code&gt;any of&lt;/code&gt; → &lt;code&gt;banking-sentinel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add filter: &lt;code&gt;Name&lt;/code&gt; → &lt;code&gt;any of&lt;/code&gt; → &lt;code&gt;banking-sentinel-chat&lt;/code&gt; — targets only the root span; avoids double-scoring the inner Strands generation (both carry the tag but have different names)&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Sampling&lt;/strong&gt; (100% is fine for PoC — reduce in production to control costs)&lt;/li&gt;
&lt;li&gt;Map the evaluator's prompt variables to the trace fields accordingly&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Execute&lt;/code&gt; — scores existing matching observations immediately and new ones going forward&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Traces&lt;/strong&gt; and click a &lt;code&gt;banking-sentinel-chat&lt;/code&gt; trace. The &lt;code&gt;banking-sentinel-helpfulness&lt;/code&gt; score shows as a small badge on the root span, with the full detail in the &lt;strong&gt;Scores&lt;/strong&gt; tab:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qs0b43nij9te6z8js3o.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qs0b43nij9te6z8js3o.png" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The online evaluations themselves produce traces — each LLM-as-judge call is a generation Langfuse records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;a href="https://langfuse.com/docs/scores/model-based-evals" rel="noopener noreferrer"&gt;Langfuse API&lt;/a&gt; to create evaluators and rules programmatically is unstable and &lt;strong&gt;only available on Langfuse Cloud&lt;/strong&gt; — not in self-hosted deployments.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 6: External Evaluations
&lt;/h3&gt;

&lt;p&gt;Langfuse lets you attach scores to any trace programmatically from your own code using &lt;code&gt;langfuse.create_score()&lt;/code&gt;. Common use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User feedback&lt;/strong&gt; — 👍/👎 ratings from end users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrail results&lt;/strong&gt; — PII checks, content policy, format validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent self-scoring&lt;/strong&gt; — quality signal computed inline and submitted back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom pipelines&lt;/strong&gt; — any score your application logic can produce&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this PoC we implement &lt;strong&gt;user feedback&lt;/strong&gt; as our example. Automated evaluators catch a lot, but human judgment is still essential — collecting 👍/👎 feedback from real users gives you a ground-truth signal that no automated evaluator can fully replace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;/chat&lt;/code&gt; endpoint returns a &lt;code&gt;trace_id&lt;/code&gt; in every response (read from &lt;code&gt;span.trace_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The chat UI attaches 👍/👎 buttons to each assistant message, keyed to that &lt;code&gt;trace_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;On click, the UI posts to &lt;code&gt;/feedback&lt;/code&gt; with &lt;code&gt;value: 1.0&lt;/code&gt; (👍) or &lt;code&gt;value: 0.0&lt;/code&gt; (👎)&lt;/li&gt;
&lt;li&gt;The backend (&lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/src/banking_sentinel/api.py" rel="noopener noreferrer"&gt;&lt;code&gt;api.py&lt;/code&gt;&lt;/a&gt;) calls &lt;code&gt;langfuse.create_score()&lt;/code&gt; — the score appears on the trace immediately
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;feedback_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FeedbackRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;langfuse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# 1.0 = 👍, 0.0 = 👎
&lt;/span&gt;        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the chat UI, send a message and click 👍 or 👎 on the reply. Then open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Traces&lt;/strong&gt; and click that trace. The &lt;code&gt;user-feedback&lt;/code&gt; score shows as a small badge on the root span, with the full detail in the &lt;strong&gt;Scores&lt;/strong&gt; tab:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w6y7hu6dt5uufi732sj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w6y7hu6dt5uufi732sj.png" width="798" height="247"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 7: Annotation Queues
&lt;/h3&gt;

&lt;p&gt;Annotation queues are a human review workflow — domain experts manually score traces to build ground truth, validate LLM-as-judge results, or investigate failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key concept:&lt;/strong&gt; Langfuse provides the queue infrastructure and a programmatic API to add items — but there are no automatic routing rules or triggers built in. Items only enter a queue through an explicit call, either from the UI (ad-hoc) or from your code. &lt;strong&gt;Your code owns the routing logic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Common triggers you can implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User gives 👎 — route negative feedback traces for human investigation&lt;/li&gt;
&lt;li&gt;Experiment score below threshold — enqueue failing traces to build better ground truth&lt;/li&gt;
&lt;li&gt;Online evaluator scores low — poll scores and enqueue traces below a quality bar&lt;/li&gt;
&lt;li&gt;Specific intent detected — route traces matching certain patterns (complaints, edge cases)&lt;/li&gt;
&lt;li&gt;Random sampling — periodically enqueue a % of production traces for ongoing quality checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1 — Create the queue (once, idempotent):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.langfuse.create_annotation_queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;quality&lt;/code&gt; score config (numeric, 0–1) and the &lt;code&gt;banking-sentinel-review&lt;/code&gt; queue that uses it, so reviewers score each trace on &lt;code&gt;quality&lt;/code&gt; — you could attach more score configs for other dimensions (see &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/evals/langfuse/create_annotation_queue.py" rel="noopener noreferrer"&gt;&lt;code&gt;evals/langfuse/create_annotation_queue.py&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;ANNOTATION_QUEUE_ID&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; to the returned queue ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 — Enqueue traces (example: on 👎):&lt;/strong&gt;&lt;br&gt;
In this PoC, the &lt;code&gt;/feedback&lt;/code&gt; endpoint in &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/src/banking_sentinel/api.py" rel="noopener noreferrer"&gt;&lt;code&gt;api.py&lt;/code&gt;&lt;/a&gt; adds the trace to the queue whenever the user gives a thumbs down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;_annotation_queue_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;langfuse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotation_queues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_queue_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;_annotation_queue_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;object_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AnnotationQueueObjectType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TRACE&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;&lt;strong&gt;3 — Review the queue:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Human Annotation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;banking-sentinel-review&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Review the conversation&lt;/li&gt;
&lt;li&gt;Assign a &lt;code&gt;quality&lt;/code&gt; score (and optionally add a comment)&lt;/li&gt;
&lt;li&gt;Correct the output if necessary&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Mark Completed&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Scores and corrected output appear on the trace and contribute to your evaluation dashboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Annotating an item — assign the &lt;code&gt;quality&lt;/code&gt; score and an optional comment:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fldf8u39hk6kura94t00q.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fldf8u39hk6kura94t00q.png" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;quality&lt;/code&gt; score then shows as a small badge on the root &lt;code&gt;banking-sentinel-chat&lt;/code&gt; span:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpt1k7drv32gno5ox8j5.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpt1k7drv32gno5ox8j5.png" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 8: Prompt Management
&lt;/h3&gt;

&lt;p&gt;Langfuse can store and version system prompts independently of your code — iterate on the prompt without redeploying the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 — Create the prompt programmatically:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; evals.langfuse.create_prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each run creates a new version (see &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/evals/langfuse/create_prompt.py" rel="noopener noreferrer"&gt;&lt;code&gt;evals/langfuse/create_prompt.py&lt;/code&gt;&lt;/a&gt;). The &lt;code&gt;production&lt;/code&gt; label is set automatically, so it is served at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 — Or create the prompt via the UI:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; → &lt;strong&gt;Prompts&lt;/strong&gt; → &lt;strong&gt;New prompt&lt;/strong&gt;, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name it &lt;code&gt;banking-sentinel-system&lt;/code&gt;, type &lt;code&gt;Text&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Paste the template using &lt;code&gt;{{variable}}&lt;/code&gt; syntax (Mustache)&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;production&lt;/code&gt; label&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either way, the prompt appears in &lt;strong&gt;Prompts&lt;/strong&gt;, versioned and labeled &lt;code&gt;production&lt;/code&gt;:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnaud97w44fyltd5f26q.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnaud97w44fyltd5f26q.png" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 — Use the prompt from code:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;USE_LANGFUSE_PROMPT=true&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;create_agent()&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/src/banking_sentinel/agent.py" rel="noopener noreferrer"&gt;&lt;code&gt;agent.py&lt;/code&gt;&lt;/a&gt; fetches the prompt from Langfuse; otherwise it uses the hardcoded template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;langfuse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_tier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reference_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&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;langfuse&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USE_LANGFUSE_PROMPT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&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;# fetch the versioned prompt via langfuse.get_prompt(...)
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# use the hardcoded template
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns &lt;code&gt;prompt_obj&lt;/code&gt;, which &lt;a href="https://github.com/rogervinas/strands-agents-langfuse-evaluations/tree/main/src/banking_sentinel/api.py" rel="noopener noreferrer"&gt;&lt;code&gt;api.py&lt;/code&gt;&lt;/a&gt; will pass back to Langfuse to link the prompt version to the trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt_obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trace then shows a &lt;code&gt;Prompt: banking-sentinel-system - v1&lt;/code&gt; tag linking to the exact version used:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filpvaqngr65r0gapsijf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filpvaqngr65r0gapsijf.png" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Benefits: version history, compare prompt versions across experiments, iterate without redeploying, A/B test prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  CI/CD
&lt;/h2&gt;

&lt;p&gt;For this PoC, CI runs three sequential jobs that gate on each other — each stage must pass before the next starts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt; — installs dependencies, builds the package, runs unit tests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standalone Evals&lt;/strong&gt; — runs Strands native evaluations in embedded mode (no Langfuse). Fails if any score drops below a threshold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Langfuse Evals&lt;/strong&gt; — runs Langfuse experiments and reports results to the dashboard. Fails if any score drops below a threshold. For this PoC we start Langfuse with Docker just for the CI run, but a real setup would point at a shared, always-on instance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means a code or prompt change that degrades agent quality will fail CI before it can reach production.&lt;/p&gt;

&lt;p&gt;Langfuse acts as an active quality gate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traces, scores, and experiment history accumulate across every PR and deploy — production traces surface regressions and datasets grow from real failures.&lt;/li&gt;
&lt;li&gt;A deployment job runs only after all eval jobs pass, so CI blocks deploys that would degrade quality — with score thresholds tuned per metric as you build up baseline data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the continuous &lt;a href="https://langfuse.com/academy/ai-engineering-loop" rel="noopener noreferrer"&gt;AI Engineering Loop&lt;/a&gt; as documented by Langfuse:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://langfuse.com/academy/ai-engineering-loop" rel="noopener noreferrer"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk3hhu1i0hd1gls3rvme3.png" width="706" height="227"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/" rel="noopener noreferrer"&gt;Strands Agents docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs" rel="noopener noreferrer"&gt;Langfuse docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/integrations/frameworks/strands-agents" rel="noopener noreferrer"&gt;Langfuse × Strands Agents integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/get-started/quick-start/" rel="noopener noreferrer"&gt;Strands Agents quick start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs/tracing" rel="noopener noreferrer"&gt;Langfuse tracing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/latest/documentation/docs/deploy/evaluation/" rel="noopener noreferrer"&gt;Strands Evals SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs/datasets" rel="noopener noreferrer"&gt;Langfuse datasets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs/scores/model-based-evals" rel="noopener noreferrer"&gt;Langfuse model-based evaluations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs/scores/user-feedback" rel="noopener noreferrer"&gt;Langfuse user feedback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs/evaluation/evaluation-methods/annotation-queues" rel="noopener noreferrer"&gt;Langfuse annotation queues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://langfuse.com/docs/prompt-management/get-started" rel="noopener noreferrer"&gt;Langfuse prompt management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy GenAI coding! 💙&lt;/p&gt;

</description>
      <category>langfuse</category>
      <category>strandsagents</category>
      <category>evaluations</category>
      <category>ai</category>
    </item>
    <item>
      <title>Spring Boot AI Evaluation Testing</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Tue, 22 Apr 2025 07:26:28 +0000</pubDate>
      <link>https://dev.to/rogervinas/spring-boot-ai-evaluation-testing-2ijk</link>
      <guid>https://dev.to/rogervinas/spring-boot-ai-evaluation-testing-2ijk</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/rogervinas/spring-boot-ai" rel="noopener noreferrer"&gt;See source code at Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, inspired by &lt;a href="https://youtu.be/Y291afdLroQ?si=3xFBJo0Nfa-RmPkV" rel="noopener noreferrer"&gt;Building Agents with AWS: Complete Tutorial&lt;/a&gt;, we will build a simple AI agent application using &lt;a href="https://docs.spring.io/spring-ai/reference/index.html" rel="noopener noreferrer"&gt;Spring AI&lt;/a&gt;, highlighting key features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.spring.io/spring-ai/reference/api/chatclient.html" rel="noopener noreferrer"&gt;Chat Client API&lt;/a&gt; and its &lt;a href="https://docs.spring.io/spring-ai/reference/api/advisors.html" rel="noopener noreferrer"&gt;Advisors&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Model Context Protocol (&lt;a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Retrieval Augmented Generation (&lt;a href="https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html" rel="noopener noreferrer"&gt;RAG&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Testing with &lt;a href="https://docs.spring.io/spring-ai/reference/api/testing.html" rel="noopener noreferrer"&gt;AI Model Evaluation&lt;/a&gt; 🤩&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The original example uses &lt;a href="https://aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;AWS Bedrock&lt;/a&gt;, but one of the great things about &lt;strong&gt;Spring AI&lt;/strong&gt; is that with just a few config tweaks, the same code works with any other supported model. This project supports three profiles: &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; (local), &lt;a href="https://ai.google.dev/" rel="noopener noreferrer"&gt;Google Gemini&lt;/a&gt; and &lt;a href="https://aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;AWS Bedrock&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The application features an AI agent that helps users book accommodations in tourist destinations.&lt;/p&gt;

&lt;p&gt;Through &lt;strong&gt;MCP&lt;/strong&gt;, the agent can use the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weather Tool&lt;/strong&gt;: Retrieves weather information for a specific city and date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking Tool&lt;/strong&gt;: Books accommodations in a city for a specific date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Weather&lt;/strong&gt; tool will be implemented locally using &lt;strong&gt;MCP&lt;/strong&gt;, while the &lt;strong&gt;Booking&lt;/strong&gt; tool will be provided by a remote &lt;strong&gt;MCP server&lt;/strong&gt;. The current date is provided via the system prompt. Additional information about cities will be retrieved from a vector store using &lt;strong&gt;RAG&lt;/strong&gt;.&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%2Fprgykwrz1an1tsf9sl3j.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%2Fprgykwrz1an1tsf9sl3j.png" alt="Diagram" width="600" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Implementation

&lt;ul&gt;
&lt;li&gt;MCP Server&lt;/li&gt;
&lt;li&gt;
Chat Server

&lt;ul&gt;
&lt;li&gt;
Components

&lt;ul&gt;
&lt;li&gt;Weather Tool&lt;/li&gt;
&lt;li&gt;Booking Tool&lt;/li&gt;
&lt;li&gt;Chat Client&lt;/li&gt;
&lt;li&gt;Chat Service&lt;/li&gt;
&lt;li&gt;Chat Controller&lt;/li&gt;
&lt;li&gt;Vector Store Initializer&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

Configuration

&lt;ul&gt;
&lt;li&gt;Ollama profile&lt;/li&gt;
&lt;li&gt;Gemini profile&lt;/li&gt;
&lt;li&gt;Bedrock profile&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Test

&lt;ul&gt;
&lt;li&gt;Test MCP Server&lt;/li&gt;
&lt;li&gt;Test Chat Server&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Run

&lt;ul&gt;
&lt;li&gt;Running the vector database&lt;/li&gt;
&lt;li&gt;Running Ollama locally&lt;/li&gt;
&lt;li&gt;Running the application&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;How to use other AI models&lt;/li&gt;

&lt;li&gt;Documentation&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  MCP Server
&lt;/h2&gt;

&lt;p&gt;As is often the case with Spring Boot, implementing the &lt;strong&gt;MCP Server&lt;/strong&gt; is pretty straightforward. Following the &lt;a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html" rel="noopener noreferrer"&gt;MCP Server Boot Starter&lt;/a&gt; guide, you just need to:&lt;/p&gt;

&lt;p&gt;1) Add the &lt;code&gt;spring-ai-starter-mcp-server-webflux&lt;/code&gt; or &lt;code&gt;spring-ai-starter-mcp-server-webmvc&lt;/code&gt; dependency&lt;/p&gt;

&lt;p&gt;2) Create an instance and annotate it with &lt;code&gt;@Tool&lt;/code&gt; and &lt;code&gt;@ToolParam&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt; &lt;span class="c1"&gt;// or @Bean / @Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookingTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bookingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BookingService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"make a reservation for accommodation for a given city and date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the city to make the reservation for"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the check-in date, when the reservation begins"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;checkinDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the check-out date, when the reservation ends"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;checkoutDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bookingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;checkinDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;checkoutDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Delegate to a service&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Register it as a &lt;code&gt;MethodToolCallbackProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookingToolConfiguration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bookingToolCallbackProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookingTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BookingTool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;MethodToolCallbackProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toolObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookingTool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&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;
  
  
  Chat Server
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Chat Server&lt;/strong&gt; is a Spring Boot application built with the following dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spring-boot-starter-web&lt;/code&gt; or &lt;code&gt;-webflux&lt;/code&gt; - to expose a REST API for the chat interface&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring-ai-starter-mcp-client&lt;/code&gt; - to use MCP&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring-ai-starter-vector-store-pgvector&lt;/code&gt; and &lt;code&gt;spring-ai-advisors-vector-store&lt;/code&gt; - to enable RAG with PGVector&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring-ai-starter-model-ollama&lt;/code&gt; - to use Ollama models&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring-ai-starter-model-google-genai&lt;/code&gt; and &lt;code&gt;spring-ai-starter-model-google-genai-embedding&lt;/code&gt; - to use Google Gemini models&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spring-ai-starter-model-bedrock&lt;/code&gt; and &lt;code&gt;spring-ai-starter-model-bedrock-converse&lt;/code&gt; - to use AWS Bedrock models&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Tools&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Weather Tool&lt;/strong&gt; - a local MCP tool that queries a &lt;strong&gt;WeatherService&lt;/strong&gt; for the weather in a given city on a given date&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking Tool&lt;/strong&gt; - a remote MCP tool that connects to the Booking MCP Server to reserve accommodations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Chat&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chat Client&lt;/strong&gt; - a &lt;strong&gt;Spring AI&lt;/strong&gt; ChatClient configured with:

&lt;ul&gt;
&lt;li&gt;A system prompt to define the AI agent’s role and the current date&lt;/li&gt;
&lt;li&gt;The AI model autoconfigured by Spring Boot via the active profile&lt;/li&gt;
&lt;li&gt;The above MCP tools as part of the AI agent’s toolset&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Chat Service&lt;/strong&gt; - wraps the &lt;strong&gt;Chat Client&lt;/strong&gt; and adds three advisors:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;QuestionAnswerAdvisor&lt;/strong&gt; - fetches context from a vector store and augments the user input (RAG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessageChatMemoryAdvisor&lt;/strong&gt; - adds conversation history to the user input (chat memory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SimpleLoggerAdvisor&lt;/strong&gt; - logs the chat history to the console (for debugging)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Chat Controller&lt;/strong&gt; - exposes a simple REST POST endpoint that takes user input, calls the &lt;strong&gt;Chat Service&lt;/strong&gt;, and returns the AI agent’s response&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Vector Store Initializer&lt;/strong&gt; - loads some sample data into the vector store at startup&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Let's implement this step by step ...&lt;/p&gt;

&lt;h3&gt;
  
  
  Weather Tool
&lt;/h3&gt;

&lt;p&gt;Here's how the &lt;strong&gt;Weather Tool&lt;/strong&gt; is implemented:&lt;/p&gt;

&lt;p&gt;1) Create an instance and annotate it with &lt;code&gt;@Tool&lt;/code&gt; and &lt;code&gt;@ToolParam&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt; &lt;span class="c1"&gt;// or @Bean / @Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;weatherService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WeatherService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"get the weather for a given city and date"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the city to get the weather for"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@ToolParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the date to get the weather for"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;weatherService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Delegate to a service&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Register it as a &lt;code&gt;MethodToolCallbackProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherToolConfiguration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;weatherToolCallbackProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weatherTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WeatherTool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;MethodToolCallbackProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toolObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weatherTool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&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;h3&gt;
  
  
  Booking Tool
&lt;/h3&gt;

&lt;p&gt;To set up the &lt;strong&gt;Booking Tool&lt;/strong&gt; as a remote MCP tool, we just need to configure the MCP client &lt;a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html#_sse_transport_properties" rel="noopener noreferrer"&gt;SSE connection&lt;/a&gt;  in &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.ai.mcp.client.toolcallback.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.mcp.client.sse.connections.booking-tool.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8081&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find all the alternative configurations in &lt;a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html" rel="noopener noreferrer"&gt;MCP Client Boot Starter&lt;/a&gt; documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat Client
&lt;/h3&gt;

&lt;p&gt;We create the &lt;strong&gt;Chat Client&lt;/strong&gt; using &lt;strong&gt;Spring AI&lt;/strong&gt;'s &lt;code&gt;ChatClient.Builder&lt;/code&gt;, which is already autoconfigured via &lt;code&gt;spring.ai&lt;/code&gt; configuration properties (we'll talk at that later in Configuration), and initialize it with a custom system prompt and the available MCP tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatClientConfiguration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;toolCallbackProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToolCallbackProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolCallbackProviders&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;toolCallbackProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToolCallbackProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;system&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
      You are an AI powered assistant to help people book accommodation in touristic cities around the world.
      If there is no information, then return a polite response suggesting you don't know.
      If the response involves a timestamp, be sure to convert it to something human-readable.
      Do not include any indication of what you're thinking.
      Use the tools available to you to answer the questions.
      Just give the answer.
      When booking accommodation for a weekend, assume check-in on Saturday and check-out on Monday.
      Current date: {currentDate}
      """&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trimIndent&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;builder&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultToolCallbacks&lt;/span&gt;&lt;span class="p"&gt;(*&lt;/span&gt;&lt;span class="n"&gt;toolCallbackProviders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toTypedArray&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;h3&gt;
  
  
  Chat Service
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Chat Service&lt;/strong&gt; exposes a single &lt;code&gt;chat&lt;/code&gt; method that takes a chat ID and a user question. It calls the &lt;strong&gt;Chat Client&lt;/strong&gt; with the user question along with a set of advisors to enrich the interaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;QuestionAnswerAdvisor&lt;/strong&gt; - retrieves relevant context from a vector store and injects it to the context (RAG)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MessageChatMemoryAdvisor&lt;/strong&gt; - adds conversation history to the user input (chat memory) for the given chat ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SimpleLoggerAdvisor&lt;/strong&gt; - logs internal advisor traces to the console (if &lt;code&gt;logging.level.org.springframework.ai.chat.client.advisor&lt;/code&gt; is set to &lt;code&gt;DEBUG&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, the question and answer are logged to the console.&lt;/p&gt;

&lt;p&gt;Here’s the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt; &lt;span class="c1"&gt;// or @Bean / @Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;questionAnswerAdvisor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QuestionAnswerAdvisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;simpleLoggerAdvisor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleLoggerAdvisor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;chatMemoryAdvisor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageChatMemoryAdvisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;MessageWindowChatMemory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chatMemoryRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InMemoryChatMemoryRepository&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&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;chatClient&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"currentDate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;advisors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questionAnswerAdvisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chatMemoryAdvisor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;simpleLoggerAdvisor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;advisors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ChatMemory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CONVERSATION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chat #$chatId question: $question"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Chat #$chatId answer: $this"&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chat Controller
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Chat Controller&lt;/strong&gt; exposes a simple REST POST endpoint that takes user input, calls the &lt;strong&gt;Chat Service&lt;/strong&gt;, and returns the AI agent’s response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;chatService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ChatService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{chatId}/chat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="n"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&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;chatService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&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;h3&gt;
  
  
  Vector Store Initializer
&lt;/h3&gt;

&lt;p&gt;It's as simple as using &lt;strong&gt;Spring AI&lt;/strong&gt;’s autoconfigured &lt;code&gt;VectorStore&lt;/code&gt; and adding documents to it. This automatically invokes the embedding model to generate embeddings and store them in the vector store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;vectorStoreInitializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VectorStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="nc"&gt;ApplicationRunner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO check if the vector store is empty ...&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO load cities from a JSON file or any other source ...&lt;/span&gt;
    &lt;span class="n"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"name: ${city.name} "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"country: ${city.country} "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"description: ${city.description}"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&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;You can find the full version of &lt;code&gt;vectorStoreInitializer&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/kotlin/com/rogervinas/ChatServerApplication.kt" rel="noopener noreferrer"&gt;ChatServerApplication.kt&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuration
&lt;/h1&gt;

&lt;p&gt;We use &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/resources/application.properties" rel="noopener noreferrer"&gt;application.properties&lt;/a&gt; instead of &lt;code&gt;application.yml&lt;/code&gt; because YAML cannot have both &lt;code&gt;spring.ai.model.embedding&lt;/code&gt; (scalar, used by Bedrock and Ollama) and &lt;code&gt;spring.ai.model.embedding.text&lt;/code&gt; (nested, used by Gemini) at the same time. Maybe some day this will be solved, but anyway this is a PoC that supports multiple models — in a production application you'd likely use only one, so this wouldn't be an issue.&lt;/p&gt;

&lt;p&gt;In this file, we define global configuration values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable all model auto-configurations by default.&lt;/li&gt;
&lt;li&gt;Configure the datasource to connect to a PostgreSQL database with PGVector support.&lt;/li&gt;
&lt;li&gt;Set the server port to &lt;code&gt;8080&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure the URL of the remote &lt;strong&gt;Booking Tool&lt;/strong&gt; MCP server.&lt;/li&gt;
&lt;li&gt;Set the logging level for chat advisor debug traces.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.application.name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;chat-server&lt;/span&gt;

&lt;span class="py"&gt;spring.datasource.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:postgresql://localhost:5432/postgres&lt;/span&gt;
&lt;span class="py"&gt;spring.datasource.username&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="py"&gt;spring.datasource.password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;
&lt;span class="py"&gt;spring.datasource.driver-class-name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;org.postgresql.Driver&lt;/span&gt;

&lt;span class="py"&gt;spring.ai.model.chat&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.model.embedding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.model.embedding.text&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;

&lt;span class="py"&gt;spring.ai.mcp.client.toolcallback.enabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;spring.ai.mcp.client.sse.connections.booking-tool.url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8081&lt;/span&gt;

&lt;span class="py"&gt;server.port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;

&lt;span class="py"&gt;logging.level.org.springframework.ai.chat.client.advisor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;INFO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI model is configured via Spring profiles. Each profile sets the chat model, embedding model, and vector store dimensions. The active profile must be specified at runtime using &lt;code&gt;SPRING_PROFILES_ACTIVE&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ollama profile
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/resources/application-ollama.yml" rel="noopener noreferrer"&gt;application-ollama.yml&lt;/a&gt;, we configure &lt;strong&gt;Spring AI&lt;/strong&gt; to use Ollama models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the base URL for the Ollama server to &lt;code&gt;http://localhost:11434&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the chat model to &lt;a href="https://ollama.com/library/llama3.1:8b" rel="noopener noreferrer"&gt;llama3.1:8b&lt;/a&gt; (must be a &lt;strong&gt;tools&lt;/strong&gt;-enabled model).&lt;/li&gt;
&lt;li&gt;Set the embedding model to &lt;a href="https://ollama.com/library/nomic-embed-text" rel="noopener noreferrer"&gt;nomic-embed-text&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pull-model-strategy: when_missing&lt;/code&gt; to only pull models if they are not available locally.&lt;/li&gt;
&lt;li&gt;Configure PGVector as the vector store with 768 dimensions (matching the embedding model size).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama"&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama"&lt;/span&gt;
    &lt;span class="na"&gt;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434"&lt;/span&gt;
      &lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;pull-model-strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;when_missing"&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama3.1:8b"&lt;/span&gt;
      &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text"&lt;/span&gt;
    &lt;span class="na"&gt;vectorstore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pgvector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;table-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector_store_ollama"&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768&lt;/span&gt;
        &lt;span class="na"&gt;initialize-schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gemini profile
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/resources/application-gemini.yml" rel="noopener noreferrer"&gt;application-gemini.yml&lt;/a&gt;, we configure &lt;strong&gt;Spring AI&lt;/strong&gt; to use Google Gemini models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the Google API key from the &lt;code&gt;GOOGLE_API_KEY&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;li&gt;Set the chat model to &lt;code&gt;gemini-2.5-flash&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the embedding model to &lt;code&gt;gemini-embedding-001&lt;/code&gt; with 768 dimensions.&lt;/li&gt;
&lt;li&gt;Configure PGVector as the vector store with 768 dimensions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-genai"&lt;/span&gt;
      &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google-genai"&lt;/span&gt;
    &lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;genai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${GOOGLE_API_KEY}"&lt;/span&gt;
        &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash"&lt;/span&gt;
            &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;
        &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${GOOGLE_API_KEY}"&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-embedding-001"&lt;/span&gt;
              &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768&lt;/span&gt;
    &lt;span class="na"&gt;vectorstore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pgvector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;table-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector_store_gemini"&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768&lt;/span&gt;
        &lt;span class="na"&gt;initialize-schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bedrock profile
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/resources/application-bedrock.yml" rel="noopener noreferrer"&gt;application-bedrock.yml&lt;/a&gt;, we configure &lt;strong&gt;Spring AI&lt;/strong&gt; to use AWS Bedrock models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the AWS credentials and region from environment variables.&lt;/li&gt;
&lt;li&gt;Set the chat model using Bedrock Converse API.&lt;/li&gt;
&lt;li&gt;Set the embedding model using Bedrock Cohere.&lt;/li&gt;
&lt;li&gt;Configure PGVector as the vector store with 1024 dimensions (matching the Cohere embedding model size).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-cohere"&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-converse"&lt;/span&gt;
    &lt;span class="na"&gt;bedrock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS_ACCESS_KEY_ID}"&lt;/span&gt;
        &lt;span class="na"&gt;secret-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS_SECRET_ACCESS_KEY}"&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS_REGION:eu-central-1}"&lt;/span&gt;
      &lt;span class="na"&gt;converse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS_BEDROCK_CHAT_MODEL}"&lt;/span&gt;
            &lt;span class="na"&gt;max-tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt;
      &lt;span class="na"&gt;cohere&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${AWS_BEDROCK_EMBEDDING_MODEL}"&lt;/span&gt;
    &lt;span class="na"&gt;vectorstore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pgvector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;table-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector_store_bedrock"&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;
        &lt;span class="na"&gt;initialize-schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Test
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Test MCP Server
&lt;/h2&gt;

&lt;p&gt;To test the &lt;strong&gt;MCP Server&lt;/strong&gt;, we will use a &lt;code&gt;McpClient&lt;/code&gt; to call the &lt;code&gt;book&lt;/code&gt; method of the &lt;strong&gt;Booking Tool&lt;/strong&gt;, mocking the downstream &lt;strong&gt;BookingService&lt;/strong&gt;:&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%2F3q7cg1lq88hes256702f.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%2F3q7cg1lq88hes256702f.png" alt="MCP Server Test" width="510" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the simplified test implementation below. For the complete implementation, including a test that verifies the list of available tools, refer to &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/mcp-server/src/test/kotlin/com/rogervinas/McpServerApplicationTest.kt" rel="noopener noreferrer"&gt;McpServerApplicationTest.kt&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webEnvironment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RANDOM_PORT&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;McpServerApplicationTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Inject the server port (it is random)&lt;/span&gt;
  &lt;span class="nd"&gt;@LocalServerPort&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Mock the BookingService instance&lt;/span&gt;
  &lt;span class="nd"&gt;@MockitoBean&lt;/span&gt;
  &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;bookingService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BookingService&lt;/span&gt;

  &lt;span class="nd"&gt;@Test&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`should&lt;/span&gt; &lt;span class="nf"&gt;book`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 3. Create a McpClient connected to the server&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nc"&gt;HttpClientSseClientTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:$port"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Mock the bookingService using argument captors&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bookResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Booking is done!"&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cityCaptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argumentCaptor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;checkinDateCaptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argumentCaptor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;checkoutDateCaptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argumentCaptor&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;doReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whenever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookingService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;cityCaptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;checkinDateCaptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;checkoutDateCaptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Call the tool&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;city&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Barcelona"&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;checkinDate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2025-04-15"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;checkoutDate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2025-04-18"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CallToolRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"city"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"checkinDate"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;checkinDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toEpochDay&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s"&gt;"checkoutDate"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;checkoutDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toEpochDay&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;// 6. Verify the result&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isFalse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;singleElement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInstanceOfSatisfying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextContent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO why is text double quoted?&lt;/span&gt;
        &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\"$bookResult\""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 7. Verify that the bookingService was called with&lt;/span&gt;
    &lt;span class="c1"&gt;// the correct parameters&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cityCaptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allValues&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;singleElement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkinDateCaptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allValues&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;singleElement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkinDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkoutDateCaptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allValues&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;singleElement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkoutDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 8. Close the client&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Chat Server
&lt;/h2&gt;

&lt;p&gt;To test the &lt;strong&gt;Chat Server&lt;/strong&gt;, we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Override the default profile to &lt;code&gt;gemini&lt;/code&gt; and disable the MCP client in &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/test/resources/application-test.yml" rel="noopener noreferrer"&gt;application-test.yml&lt;/a&gt; (overridable via &lt;code&gt;SPRING_PROFILES_ACTIVE&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Replace the remote &lt;strong&gt;Booking Tool&lt;/strong&gt; by a local &lt;strong&gt;Booking Test Tool&lt;/strong&gt; with the same signature.

&lt;ul&gt;
&lt;li&gt;Create the local &lt;strong&gt;Book Testing Tool&lt;/strong&gt; in &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/main/kotlin/com/rogervinas/BookingTestToolConfiguration.kt" rel="noopener noreferrer"&gt;BookingTestToolConfiguration.kt&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Mock the downstream services &lt;strong&gt;Weather Service&lt;/strong&gt; and &lt;strong&gt;Booking Service&lt;/strong&gt; with &lt;code&gt;MockitoBean&lt;/code&gt;.&lt;/li&gt;

&lt;li&gt;Create a fixed &lt;strong&gt;Clock&lt;/strong&gt; to control the current date in &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/test/kotlin/com/rogervinas/configuration/ClockTestToolConfiguration.kt" rel="noopener noreferrer"&gt;ClockTestToolConfiguration.kt&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Declare it as &lt;code&gt;@Primary @Bean&lt;/code&gt; to override the default &lt;code&gt;Clock&lt;/code&gt; bean.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Start Docker Compose with PGVector using &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;Testcontainers&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&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%2Fu4a1xa58gtgsjv74x1b0.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%2Fu4a1xa58gtgsjv74x1b0.png" alt="Chat Server Test" width="750" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might’ve noticed that the test doesn’t actually check the MCP client’s SSE connection as it is disabled. I tried spinning up an &lt;code&gt;McpServer&lt;/code&gt; using &lt;code&gt;McpServerAutoConfiguration&lt;/code&gt;, and it almost worked. The problem? The client tries to connect before the server is up, which causes the whole application to fail on startup. Maybe is just an ordering issue, and hopefully something that can be fixed in the future 🤞&lt;/p&gt;

&lt;p&gt;Now for the interesting part, how do we test the AI agent’s response? This is where &lt;a href="https://docs.spring.io/spring-ai/reference/api/testing.html" rel="noopener noreferrer"&gt;Evaluation Testing&lt;/a&gt; comes in:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One method to evaluate the response is to use the AI model itself for evaluation. Select the best AI model for the evaluation, which may not be the same model used to generate the response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This aligns with the &lt;strong&gt;evaluation techniques&lt;/strong&gt; described in Martin Fowler’s &lt;a href="https://martinfowler.com/articles/gen-ai-patterns/#evals" rel="noopener noreferrer"&gt;Evals&lt;/a&gt; GenAI pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-evaluation&lt;/strong&gt;: The LLM evaluates its own response, but this can reinforce its own mistakes or biases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM as a judge&lt;/strong&gt;: Another model scores the output, reducing bias by introducing a second opinion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human evaluation&lt;/strong&gt;: People manually review responses to ensure the tone and intent feel right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To keep things simple, we’ll go with self-evaluation 🤓&lt;/p&gt;

&lt;p&gt;Each test will follow this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;`should&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nf"&gt;something`&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Mock downstream service(s)&lt;/span&gt;
  &lt;span class="c1"&gt;// Optionally use argument captors depending on how you plan&lt;/span&gt;
  &lt;span class="c1"&gt;// to verify parameters in step 5&lt;/span&gt;
  &lt;span class="c1"&gt;// Example for BookingService:&lt;/span&gt;
  &lt;span class="nf"&gt;doReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Your booking is done!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whenever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookingTestService&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Call the chat service&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;chatId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;chatResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can you book accommodation for Barcelona from 2025-04-15 to 2025-04-18?"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Evaluate the response using the AI model&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;evaluationResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatClientBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;evaluationRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userSpec&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;userSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"""
      Your task is to evaluate if the answer given by an AI agent to a human user matches the claim.
      Return YES if the answer matches the claim and NO if it does not.
      After returning YES or NO, explain why.
      Assume that today is ${LocalDate.now(clock)}.
      Answer: {answer}
      Claim: {claim}
      """&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trimIndent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluationRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responseContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluationRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EvaluationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"Accommodation has been booked Barcelona from 2025-04-15 to 2025-04-18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chatResponse&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Assert the evaluation result is successful, show feedback if not&lt;/span&gt;
  &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evaluationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isPass&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isTrue&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withFailMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;evaluationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feedback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. If applicable, verify the parameters passed to the service&lt;/span&gt;
  &lt;span class="c1"&gt;// You can verify with argument captors or use the `verify` method as in the example below:&lt;/span&gt;
  &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookingTestService&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Barcelona"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2025-04-15"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2025-04-18"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the full test implementation in &lt;a href="https://github.com/rogervinas/spring-boot-ai/tree/main/chat-server/src/test/kotlin/com/rogervinas/ChatServerApplicationTest.kt" rel="noopener noreferrer"&gt;ChatServerApplicationTest.kt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each evaluation uses a custom prompt tailored to the specific response being tested, and as you experiment, you'll notice some surprisingly quirky behavior. That’s why I ended up creating a custom &lt;code&gt;TestEvaluator&lt;/code&gt;, based on Spring AI’s &lt;code&gt;RelevancyEvaluator&lt;/code&gt; and &lt;code&gt;FactCheckingEvaluator&lt;/code&gt;, which may not yet offer the level of customization you might want.&lt;/p&gt;

&lt;p&gt;I had to adjust each prompt after running into odd results. For example, the evaluation model assuming it was still 2023 and refusing to believe the AI agent could predict weather for 2025. Or it mistaking "you" in the answer as referring to itself instead of the user. The weirdest? One evaluation just answered “NO” but the explanation said, “well, maybe I should have said YES” 🤣&lt;/p&gt;

&lt;p&gt;For a production system, you'd definitely need a lot of prompt tuning and testing to get things right, for both the system and the evaluator, I suppose that’s part of the "fun" when working with GenAI.&lt;/p&gt;

&lt;p&gt;By default, tests use the Gemini profile both locally and in CI. You can also test with Ollama (results may be flaky without adequate GPU hardware) or Bedrock.&lt;/p&gt;

&lt;h1&gt;
  
  
  Run
&lt;/h1&gt;

&lt;p&gt;You can configure the application using environment variables or a &lt;code&gt;system.properties&lt;/code&gt; file in the root directory. This file is ignored by Git and is loaded by both &lt;code&gt;./gradlew bootRun&lt;/code&gt; and tests.&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;system.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="c"&gt;# AWS Bedrock
&lt;/span&gt;&lt;span class="py"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;eu-central-1&lt;/span&gt;
&lt;span class="py"&gt;AWS_BEDROCK_CHAT_MODEL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="py"&gt;AWS_BEDROCK_EMBEDDING_MODEL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;

&lt;span class="c"&gt;# Google Gemini
&lt;/span&gt;&lt;span class="py"&gt;GOOGLE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the vector database
&lt;/h2&gt;

&lt;p&gt;All profiles require PGVector for the RAG vector database:&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="nb"&gt;cd &lt;/span&gt;chat-server
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-vectordb.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stop it:&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="nb"&gt;cd &lt;/span&gt;chat-server
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-vectordb.yml down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stop it and remove all volumes (removes all vector database data):&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="nb"&gt;cd &lt;/span&gt;chat-server
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-vectordb.yml down &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running Ollama locally
&lt;/h2&gt;

&lt;p&gt;If you want to use local LLMs, you can run Ollama either via Docker Compose or as a native application (more info at &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;ollama.com&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="nb"&gt;cd &lt;/span&gt;chat-server
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-ollama.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the application
&lt;/h2&gt;

&lt;p&gt;1) Start MCP server&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="nb"&gt;cd &lt;/span&gt;mcp-server
./gradlew bootRun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Start Chat Server with one of the following profiles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; (requires Ollama and vector database):&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="nb"&gt;cd &lt;/span&gt;chat-server
&lt;span class="nv"&gt;SPRING_PROFILES_ACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ollama ./gradlew bootRun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gemini&lt;/strong&gt; (requires vector database and &lt;code&gt;GOOGLE_API_KEY&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="nb"&gt;cd &lt;/span&gt;chat-server
&lt;span class="nv"&gt;SPRING_PROFILES_ACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gemini ./gradlew bootRun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bedrock&lt;/strong&gt; (requires vector database and AWS credentials):&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="nb"&gt;cd &lt;/span&gt;chat-server
&lt;span class="nv"&gt;SPRING_PROFILES_ACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bedrock ./gradlew bootRun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Start chatting&lt;/p&gt;

&lt;p&gt;A simple chat UI is available at &lt;a href="http://localhost:8080/chat.html" rel="noopener noreferrer"&gt;http://localhost:8080/chat.html&lt;/a&gt; (disclaimer: this UI was entirely AI-generated as frontend is not the goal of this PoC).&lt;/p&gt;

&lt;p&gt;You can also explore the API endpoints at &lt;a href="http://localhost:8080/swagger-ui.html" rel="noopener noreferrer"&gt;http://localhost:8080/swagger-ui.html&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to use other AI models
&lt;/h1&gt;

&lt;p&gt;To use any of the other &lt;a href="https://docs.spring.io/spring-ai/reference/api/chatmodel.html" rel="noopener noreferrer"&gt;AI models&lt;/a&gt; supported by &lt;strong&gt;Spring AI&lt;/strong&gt;, follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the required dependencies&lt;/li&gt;
&lt;li&gt;Configure the model in its own &lt;code&gt;application-&amp;lt;model&amp;gt;.yml&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Activate the profile using &lt;code&gt;SPRING_PROFILES_ACTIVE=&amp;lt;model&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Documentation
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.spring.io/spring-ai/reference/index.html" rel="noopener noreferrer"&gt;Spring AI&lt;/a&gt; documentation&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/articles/gen-ai-patterns" rel="noopener noreferrer"&gt;Martin Fowler's GenAI patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Inspired by sample project &lt;a href="https://github.com/aws-samples/Sample-Model-Context-Protocol-Demos/tree/main/modules/spring-ai-java-bedrock-mcp-rag" rel="noopener noreferrer"&gt;spring-ai-java-bedrock-mcp-rag&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danvega/awesome-spring-ai" rel="noopener noreferrer"&gt;Awesome Spring AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy GenAI coding! 💙&lt;/p&gt;

</description>
      <category>llm</category>
      <category>rag</category>
      <category>mcp</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Tests Everywhere - Swift</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Mon, 18 Dec 2023 10:32:24 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-swift-33pp</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-swift-33pp</guid>
      <description>&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/rogervinas"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://www.swift.org/"&gt;Swift&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://developer.apple.com/documentation/xctest"&gt;XCTest&lt;/a&gt; and &lt;a href="https://github.com/Brightify/Cuckoo"&gt;Cuckoo&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift#run-this-project-using--docker"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift#run-this-project-locally"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Sorry but reading that &lt;a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics#Naming-Constants-and-Variables"&gt;constant and variable names can contain almost any character, including Unicode characters&lt;/a&gt; I could not resist 😅 ...&lt;/p&gt;

&lt;p&gt;1) Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift/Sources/HelloMessage.swift"&gt;HelloMessage.swift&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;HelloMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&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="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&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;2) Same way create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift/Sources/HelloConsole.swift"&gt;HelloConsole.swift&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;HelloConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Swift&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&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="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;3) Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="//Sources/HelloApp.swift"&gt;HelloApp.swift&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;HelloApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloMessage&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloConsole&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;👋&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;4) Create main class in &lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift/Sources/Main.swift"&gt;Main.swift&lt;/a&gt; that wraps it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&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;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://developer.apple.com/documentation/xctest"&gt;XCTest&lt;/a&gt; and &lt;a href="https://github.com/Brightify/Cuckoo"&gt;Cuckoo&lt;/a&gt; guides ...&lt;/p&gt;

&lt;p&gt;1) Mock generation&lt;/p&gt;

&lt;p&gt;Mocks have to be generated statically, you can follow &lt;a href="https://github.com/Brightify/Cuckoo/tree/master#swift-package-manager"&gt;these instructions&lt;/a&gt; or alternatively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build generator (only once, &lt;a href="https://developer.apple.com/xcode/resources/"&gt;XCode command line tools&lt;/a&gt; required):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:Brightify/Cuckoo.git /tmp/Cuckoo
/tmp/Cuckoo/build_generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate mocks (as many times as needed):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/tmp/Cuckoo/Generator/bin/cuckoo_generator generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--testable&lt;/span&gt; Hello &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; Tests/Mocks.swift &lt;span class="se"&gt;\&lt;/span&gt;
  Sources/HelloMessage.swift &lt;span class="se"&gt;\&lt;/span&gt;
  Sources/HelloConsole.swift
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also integrate mocks generation in the build, before compile phase.&lt;/p&gt;

&lt;p&gt;2) Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift/Tests/HelloMessageTest.swift"&gt;HelloMessageTest.swift&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;HelloMessageTest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;XCTestCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testHelloMessageShouldReturnHelloWorld&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kt"&gt;XCTAssertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;👋&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&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;3) Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/swift/Tests/HelloAppTest.swift"&gt;HelloAppTest.swift&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;HelloAppTest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;XCTestCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testHelloAppShouldDisplayHelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;messageText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello Test!"&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.1 Create a mock of HelloMessage&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MockHelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.2 Return "Hello Test!" whenever 👋 getter is called&lt;/span&gt;
    &lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stub&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;👋&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thenReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.2 Create a mock of HelloConsole&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MockHelloConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.3 Do nothing whenever print is called&lt;/span&gt;
    &lt;span class="nf"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stub&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stub&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;👋&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;anyString&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thenDoNothing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.4 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.5 Execute the method we want to test&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.6 Verify HelloConsole mock print() method&lt;/span&gt;
    &lt;span class="c1"&gt;// has been called once with "Hello Test!"&lt;/span&gt;
    &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;👋&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;messageText&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;4) Test output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test Suite 'All tests' started at 2023-12-12 16:48:21.225
Test Suite 'debug.xctest' started at 2023-12-12 16:48:21.227
Test Suite 'HelloAppTest' started at 2023-12-12 16:48:21.227
Test Case 'HelloAppTest.testHelloAppShouldDisplayHelloMessage' started at 2023-12-12 16:48:21.227
Test Case 'HelloAppTest.testHelloAppShouldDisplayHelloMessage' passed (0.003 seconds)
Test Suite 'HelloAppTest' passed at 2023-12-12 16:48:21.230
    Executed 1 test, with 0 failures (0 unexpected) in 0.003 (0.003) seconds
Test Suite 'HelloMessageTest' started at 2023-12-12 16:48:21.230
Test Case 'HelloMessageTest.testHelloMessageShouldReturnHelloWorld' started at 2023-12-12 16:48:21.231
Test Case 'HelloMessageTest.testHelloMessageShouldReturnHelloWorld' passed (0.001 seconds)
Test Suite 'HelloMessageTest' passed at 2023-12-12 16:48:21.232
    Executed 1 test, with 0 failures (0 unexpected) in 0.001 (0.001) seconds
Test Suite 'debug.xctest' passed at 2023-12-12 16:48:21.232
    Executed 2 tests, with 0 failures (0 unexpected) in 0.004 (0.004) seconds
Test Suite 'All tests' passed at 2023-12-12 16:48:21.232
    Executed 2 tests, with 0 failures (0 unexpected) in 0.004 (0.004) seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>swift</category>
      <category>xctest</category>
      <category>cuckoo</category>
    </item>
    <item>
      <title>Tests Everywhere - COBOL</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Mon, 11 Dec 2023 09:45:30 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-cobol-p53</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-cobol-p53</guid>
      <description>&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/rogervinas"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/COBOL"&gt;COBOL&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://github.com/openmainframeproject/cobol-check"&gt;cobol-check&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/cobol#run-this-project-using--docker"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/cobol#run-this-project-locally"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;1) Create a program &lt;code&gt;HELLO-CONSOLE&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/cobol/src/main/cobol/HELLO-CONSOLE.CBL"&gt;HELLO-CONSOLE.CBL&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declare an alpha-numeric parameter &lt;code&gt;MSG&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Program will just display &lt;code&gt;MSG&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO-CONSOLE.

DATA DIVISION.
LINKAGE SECTION.
01 MSG PIC A(20).

PROCEDURE DIVISION USING MSG.
    DISPLAY MSG.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Create a program &lt;code&gt;HELLO&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/cobol/src/main/cobol/HELLO.CBL"&gt;HELLO.CBL&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Declare an alpha-numeric variable &lt;code&gt;MSG&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;HELLO-MESSAGE&lt;/code&gt; paragraph that sets a value to &lt;code&gt;MSG&lt;/code&gt; variable&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;HELLO&lt;/code&gt; paragraph calling:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;HELLO-MESSAGE&lt;/code&gt; paragraph&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HELLO-CONSOLE&lt;/code&gt; program&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;MAIN&lt;/code&gt; paragraph just calling the &lt;code&gt;HELLO&lt;/code&gt; paragraph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 We use a &lt;a href="https://www.tutorialspoint.com/cobol/cobol_program_structure.htm"&gt;paragraph&lt;/a&gt; for &lt;code&gt;HELLO-MESSAGE&lt;/code&gt; and a &lt;a href="https://www.tutorialspoint.com/cobol/cobol_program_structure.htm"&gt;program&lt;/a&gt; for &lt;code&gt;HELLO-CONSOLE&lt;/code&gt; just to show two different options and how to mock them in the tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDENTIFICATION DIVISION.
PROGRAM-ID. HELLO.

DATA DIVISION.
WORKING-STORAGE SECTION.
01 MSG PIC A(20).

PROCEDURE DIVISION.

MAIN.
    PERFORM HELLO.
    STOP RUN.

HELLO.
    PERFORM HELLO-MESSAGE.
    CALL 'HELLO-CONSOLE' USING MSG.

HELLO-MESSAGE.
    MOVE 'HELLO WORLD!' TO MSG.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://github.com/openmainframeproject/cobol-check/wiki/A-Brief-Example"&gt;A Brief Example&lt;/a&gt;, &lt;a href="https://github.com/openmainframeproject/cobol-check/wiki/Mock-a-paragraph-and-provide-fake-results"&gt;Mock a paragraph&lt;/a&gt; and &lt;a href="https://github.com/openmainframeproject/cobol-check/wiki/Mock-a-CALL-statement-and-provide-fake-results"&gt;Mock a CALL statement&lt;/a&gt; guides ...&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/cobol/src/test/cobol/HELLO/HELLO.cut"&gt;HELLO.cut&lt;/a&gt; file:&lt;/p&gt;

&lt;p&gt;1) Test &lt;code&gt;HELLO-MESSAGE&lt;/code&gt; paragraph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestSuite "HELLO-MESSAGE"

TestCase "SHOULD RETURN HELLO WORLD"
  MOVE "" TO MSG
  PERFORM HELLO-MESSAGE
  Expect MSG = "HELLO WORLD!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) Test &lt;code&gt;HELLO&lt;/code&gt; paragraph mocking &lt;code&gt;HELLO-MESSAGE&lt;/code&gt; and &lt;code&gt;HELLO-CONSOLE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestSuite "HELLO"

TestCase "SHOULD DISPLAY HELLO MESSAGE"
  * 2.1 Mock HELLO-MESSAGE
  MOCK PARAGRAPH HELLO-MESSAGE
      MOVE "HELLO TEST!" TO MSG
  END-MOCK

  * 2.2 Mock HELLO-CONSOLE
  MOCK CALL 'HELLO-CONSOLE' USING MSG
      CONTINUE
  END-MOCK

  * 2.3 Execute the paragraph we want to test
  PERFORM HELLO

  * 2.4 Verify MSG has the expected value
  Expect MSG = "HELLO TEST!"
  * 2.5 Verify HELLO-MESSAGE has been called once
  VERIFY PARAGRAPH HELLO-MESSAGE HAPPENED ONCE
  * 2.6 Verify HELLO-CONSOLE has been called once
  VERIFY CALL 'HELLO-CONSOLE' USING MSG HAPPENED ONCE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Test output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TESTSUITE:
HELLO-MESSAGE
     PASS:   1. SHOULD RETURN HELLO WORLD
TESTSUITE:
HELLO
     PASS:   2. SHOULD DISPLAY HELLO MESSAGE
     PASS:   3. VERIFY EXACT 1 ACCESS TO PARAGRAPH HELLO-MESSAGE
     PASS:   4. VERIFY EXACT 1 ACCESS TO CALL 'HELLO-CONSOLE'

  4 TEST CASES WERE EXECUTED
  4 PASSED
  0 FAILED
  0 CALLS NOT MOCKED
================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have no idea how to unit test &lt;code&gt;HELLO-CONSOLE&lt;/code&gt; program as it seems &lt;a href="https://github.com/openmainframeproject/cobol-check"&gt;cobol-check&lt;/a&gt; does not support &lt;code&gt;PROCEDURE DIVISION USING X&lt;/code&gt; sections so I created &lt;a href="https://github.com/openmainframeproject/cobol-check/issues/339"&gt;this issue&lt;/a&gt; 🤞&lt;/p&gt;

&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>cobol</category>
      <category>cobolcheck</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tests Everywhere - Scala</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:25:51 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-scala-30ph</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-scala-30ph</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://www.scala-lang.org/" rel="noopener noreferrer"&gt;Scala&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://www.scalatest.org/" rel="noopener noreferrer"&gt;ScalaTest&lt;/a&gt; and &lt;a href="https://www.scalatest.org/plus/mockito" rel="noopener noreferrer"&gt;Mockito&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/scala#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/scala#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/scala/src/main/scala/org/hello/HelloMessage.scala" rel="noopener noreferrer"&gt;HelloMessage.scala&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt;
  &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="kt"&gt;text:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create it as a class as &lt;a href="https://www.scalatest.org/plus/mockito" rel="noopener noreferrer"&gt;Mockito&lt;/a&gt; is able to mock final classes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Same way create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/scala/src/main/scala/org/hello/HelloConsole.scala" rel="noopener noreferrer"&gt;HelloConsole.scala&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt;
  &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="kt"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;text:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="kt"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/scala/src/main/scala/org/hello/HelloApp.scala" rel="noopener noreferrer"&gt;HelloApp.scala&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt;
  &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="kt"&gt;printHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="kt"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create main class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/scala/src/main/scala/org/hello/Main.scala" rel="noopener noreferrer"&gt;Main.scala&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="nd"&gt;@main&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;printHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://www.scalatest.org/user_guide/writing_your_first_test" rel="noopener noreferrer"&gt;ScalaTest &amp;gt; Writing your first test&lt;/a&gt; and &lt;a href="https://www.scalatest.org/plus/mockito" rel="noopener noreferrer"&gt;ScalaTest + Mockito&lt;/a&gt; guides ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/scala/src/test/scala/org/hello/HelloMessageSpec.scala" rel="noopener noreferrer"&gt;HelloMessageSpec.scala&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessageSpec&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AnyFlatSpec&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="s"&gt;"A HelloMessage"&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="s"&gt;"return hello world"&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;assertResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/scala/src/test/scala/org/hello/HelloAppSpec.scala" rel="noopener noreferrer"&gt;HelloAppSpec.scala&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloAppSpec&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AnyFlatSpec&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="s"&gt;"A HelloApp"&lt;/span&gt; &lt;span class="n"&gt;should&lt;/span&gt; &lt;span class="s"&gt;"print hello message"&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;messageText&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello Test!"&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.1 Create a mock of HelloMessage&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.2 Return "Hello Test!" whenever text is called&lt;/span&gt;
    &lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;text&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.3 Create a mock of HelloConsole&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;console&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.4 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.5 Execute the method we want to test&lt;/span&gt;
    &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;printHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.6 Verify HelloConsole mock print() method&lt;/span&gt;
    &lt;span class="c1"&gt;// has been called once with "Hello Test!"&lt;/span&gt;
    &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[info] HelloMessageSpec:
[info] A HelloMessage
[info] - should return hello world
[info] HelloAppSpec:
[info] A HelloApp
[info] - should print hello message
[info] Run completed in 515 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 2, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 6 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>scala</category>
      <category>scalatest</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tests Everywhere - Rust</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:23:01 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-rust-42ee</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-rust-42ee</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://www.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://docs.rs/mockall/latest/mockall/" rel="noopener noreferrer"&gt;mockall&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/rust#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/rust#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; trait and &lt;code&gt;HelloWorldMessage&lt;/code&gt; implementation in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_message.rs" rel="noopener noreferrer"&gt;hello_message.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;HelloWorldMessage&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;HelloWorldMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&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;impl&lt;/span&gt; &lt;span class="n"&gt;HelloWorldMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;Self&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;Creating it as a trait will allow us to mock it for testing.&lt;/p&gt;

&lt;p&gt;Note that we create a &lt;code&gt;HelloWorldMessage::new()&lt;/code&gt; method just as a convention to create new instances, although we could just use &lt;code&gt;HelloWorldMessage {}&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Same way create &lt;code&gt;HelloConsole&lt;/code&gt; trait and &lt;code&gt;HelloSystemConsole&lt;/code&gt; implementation in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_console.rs" rel="noopener noreferrer"&gt;hello_console.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;HelloSystemConsole&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;HelloSystemConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&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="n"&gt;text&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;impl&lt;/span&gt; &lt;span class="n"&gt;HelloSystemConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;Self&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;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_app.rs" rel="noopener noreferrer"&gt;hello_app.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;HelloApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;HelloApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print_hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.console&lt;/span&gt;&lt;span class="nf"&gt;.print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.message&lt;/span&gt;&lt;span class="nf"&gt;.text&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 we pass constructor arguments as &lt;code&gt;Box&amp;lt;dyn T&amp;gt;&lt;/code&gt; to avoid &lt;code&gt;doesn't have a size known at compile-time&lt;/code&gt; compilation error.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create main function in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/main.rs" rel="noopener noreferrer"&gt;main.rs&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HelloWorldMessage&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HelloSystemConsole&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.print_hello&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;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html" rel="noopener noreferrer"&gt;Rust By Example &amp;gt; Unit testing&lt;/a&gt; and &lt;a href="https://docs.rs/mockall/latest/mockall/#getting-started" rel="noopener noreferrer"&gt;mockall &amp;gt; Getting Started&lt;/a&gt; guides ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_message.rs" rel="noopener noreferrer"&gt;hello_message.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;#[test]&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;should_return_hello_world&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HelloWorldMessage&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&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;ol&gt;
&lt;li&gt;Setup &lt;code&gt;HelloMessage&lt;/code&gt; mocking needed by &lt;code&gt;HelloApp&lt;/code&gt; test in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_message.rs" rel="noopener noreferrer"&gt;hello_message.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2.1 Enable automock&lt;/span&gt;
&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;mockall&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;automock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 2.2 Enable automock for HelloMessage&lt;/span&gt;
&lt;span class="nd"&gt;#[cfg_attr(test,&lt;/span&gt; &lt;span class="nd"&gt;automock)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&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;ol&gt;
&lt;li&gt;Setup &lt;code&gt;HelloConsole&lt;/code&gt; mocking needed by &lt;code&gt;HelloApp&lt;/code&gt; test in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_console.rs" rel="noopener noreferrer"&gt;hello_console.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 3.1 Enable automock&lt;/span&gt;
&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;mockall&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;automock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 3.2 Enable automock for HelloConsole&lt;/span&gt;
&lt;span class="nd"&gt;#[cfg_attr(test,&lt;/span&gt; &lt;span class="nd"&gt;automock)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&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;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/rust/src/hello_app.rs" rel="noopener noreferrer"&gt;hello_app.rs&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;mockall&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;predicate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// 4.1 Use MockHelloMessage, enabled by 2.2&lt;/span&gt;
  &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;hello_message&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MockHelloMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// 4.2 Use MockHelloConsole, enabled by 3.2&lt;/span&gt;
  &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;hello_console&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MockHelloConsole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;#[test]&lt;/span&gt;
  &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;should_print_hello_message&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello Test!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 4.3 Create a mock of HelloMessage&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;MockHelloMessage&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// 4.4 Return "Hello Test!" whenever text is called&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="nf"&gt;.expect_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.return_const&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// 4.5 Create a mock of HelloConsole&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;MockHelloConsole&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// 4.6 Expect print to be called once with "Hello Test!"&lt;/span&gt;
    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="nf"&gt;.expect_print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nf"&gt;.with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="nf"&gt;.times&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;.return_const&lt;/span&gt;&lt;span class="p"&gt;(());&lt;/span&gt;

    &lt;span class="c1"&gt;// 4.7 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nn"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// 4.8 Execute the method we want to test&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.print_hello&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Expectation 4.6 will be checked once test ends&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;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;running 2 tests
test hello_app::tests::should_print_hello_message ... ok
test hello_message::tests::should_return_hello_world ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>rust</category>
      <category>mockall</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tests Everywhere - Ruby</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:19:46 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-ruby-222h</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-ruby-222h</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://www.ruby-lang.org" rel="noopener noreferrer"&gt;Ruby&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://rspec.info/" rel="noopener noreferrer"&gt;RSpec&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/ruby#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/ruby#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/ruby/lib/HelloMessage.rb" rel="noopener noreferrer"&gt;HelloMessage.rb&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;
    &lt;span class="vi"&gt;@text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/ruby/lib/HelloConsole.rb" rel="noopener noreferrer"&gt;HelloConsole.rb&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/ruby/lib/HelloApp.rb" rel="noopener noreferrer"&gt;HelloApp.rb&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="vi"&gt;@console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;printHello&lt;/span&gt;
    &lt;span class="vi"&gt;@console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create main function in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/ruby/Main.rb" rel="noopener noreferrer"&gt;Main.rb&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://rspec.info/features/3-12/rspec-core/" rel="noopener noreferrer"&gt;RSpec Core&lt;/a&gt; and &lt;a href="https://rspec.info/features/3-12/rspec-mocks/" rel="noopener noreferrer"&gt;RSpec Mocks&lt;/a&gt; guides ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/ruby/spec/HelloMessage_spec.rb" rel="noopener noreferrer"&gt;HelloMessage_spec.rb&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;HelloMessage&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"should return hello world"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/ruby/spec/HelloApp_spec.rb" rel="noopener noreferrer"&gt;HelloApp_spec.rb&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;HelloApp&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"should print hello message"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;messageText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hello Test!"&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.1 Create a mock of HelloMessage&lt;/span&gt;
    &lt;span class="c1"&gt;# that will return "Hello Test!" whenever text is called&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HelloMessage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.2 Create a mock of HelloConsole&lt;/span&gt;
    &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HelloConsole"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 2.3 Expect print to be called once with "Hello Test!"&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:print&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.4 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;
    &lt;span class="c1"&gt;# 2.5 Execute the method we want to test&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&lt;/span&gt;

    &lt;span class="c1"&gt;# Expectation 2.3 will be checked once test ends&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HelloApp
  should print hello message

HelloMessage
  should return hello world

Finished in 0.00457 seconds (files took 0.05704 seconds to load)
2 examples, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>ruby</category>
      <category>rspec</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tests Everywhere - Python</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:15:04 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-python-1cjo</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-python-1cjo</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://docs.python.org/3/library/unittest.html" rel="noopener noreferrer"&gt;unittest&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/python#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/python#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/python/main/hello_message.py" rel="noopener noreferrer"&gt;hello_message.py&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/python/main/hello_console.py" rel="noopener noreferrer"&gt;hello_console.py&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;builtins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;__print__&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;__print__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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 we have to import &lt;code&gt;print&lt;/code&gt; system function as &lt;code&gt;__print__&lt;/code&gt; to avoid conflict with &lt;code&gt;HelloConsole&lt;/code&gt; own &lt;code&gt;print&lt;/code&gt; method.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/python/main/hello_app.py" rel="noopener noreferrer"&gt;hello_app.py&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create main function in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/python/main/__main__.py" rel="noopener noreferrer"&gt;__main__.py&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print_hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://docs.python.org/3/library/unittest.html#basic-example" rel="noopener noreferrer"&gt;unittest &amp;gt; Basic example&lt;/a&gt; and &lt;a href="https://docs.python.org/3/library/unittest.mock.html#quick-guide" rel="noopener noreferrer"&gt;unittest.mock &amp;gt; Quick Guide&lt;/a&gt; guides ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/python/test/test_hello_message.py" rel="noopener noreferrer"&gt;test_hello_message.py&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessageTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_return_hello_world&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/python/test/test_hello_app.py" rel="noopener noreferrer"&gt;test_hello_app.py&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloAppTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_print_hello_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello Test!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.1 Create a mock of HelloMessage
&lt;/span&gt;    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# 2.2 Return "Hello Test!" whenever text is called
&lt;/span&gt;    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.3 Create a mock of HelloConsole
&lt;/span&gt;    &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# 2.4 Mock print method
&lt;/span&gt;    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.5 Create a HelloApp, the one we want to test, passing the mocks
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 2.6 Execute the method we want to test
&lt;/span&gt;    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print_hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 2.7 Verify HelloConsole mock print() method
&lt;/span&gt;    &lt;span class="c1"&gt;# has been called once with "Hello Test!"
&lt;/span&gt;    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_should_print_hello_message (test.test_hello_app.HelloAppTest.test_should_print_hello_message) ... ok
test_should_return_hello_world (test.test_hello_message.HelloMessageTest.test_should_return_hello_world) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

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

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>python</category>
      <category>unittest</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tests Everywhere - Kotlin</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:12:28 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-kotlin-39ne</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-kotlin-39ne</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://kotest.io" rel="noopener noreferrer"&gt;Kotest&lt;/a&gt; and &lt;a href="https://mockk.io/" rel="noopener noreferrer"&gt;MockK&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/kotlin#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/kotlin#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/kotlin/src/main/kotlin/org/hello/HelloMessage.kt" rel="noopener noreferrer"&gt;HelloMessage.kt&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create it as a class as &lt;a href="https://mockk.io/" rel="noopener noreferrer"&gt;MockK&lt;/a&gt; is able to mock final classes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Same way create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/kotlin/src/main/kotlin/org/hello/HelloConsole.kt" rel="noopener noreferrer"&gt;HelloConsole.kt&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/kotlin/src/main/kotlin/org/hello/HelloApp.kt" rel="noopener noreferrer"&gt;HelloApp.kt&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;console&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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;ol&gt;
&lt;li&gt;Create main class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/kotlin/src/main/kotlin/org/hello/Main.kt" rel="noopener noreferrer"&gt;Main.kt&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;console&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&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;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://kotest.io/docs/framework/writing-tests.html" rel="noopener noreferrer"&gt;Kotest &amp;gt; Writing Tests&lt;/a&gt; and &lt;a href="https://mockk.io/#class-mock" rel="noopener noreferrer"&gt;MockK &amp;gt; Class mock&lt;/a&gt; guides ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/kotlin/src/test/kotlin/org/hello/HelloMessageTest.kt" rel="noopener noreferrer"&gt;HelloMessageTest.kt&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessageTest&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DescribeSpec&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HelloMessage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"should return hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="n"&gt;shouldBe&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&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;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/kotlin/src/test/kotlin/org/hello/HelloAppTest.kt" rel="noopener noreferrer"&gt;HelloAppTest.kt&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloAppTest&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DescribeSpec&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;

  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HelloApp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"should print hello message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;messageText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello Test!"&lt;/span&gt;

      &lt;span class="c1"&gt;// 2.1 Create a mock of HelloMessage&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mockk&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
      &lt;span class="c1"&gt;// 2.2 Return "Hello Test!" whenever text is called&lt;/span&gt;
      &lt;span class="nf"&gt;every&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nf"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// 2.3 Create a mock of HelloConsole&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;console&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mockk&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
      &lt;span class="c1"&gt;// 2.4 Do nothing whenever print is called&lt;/span&gt;
      &lt;span class="nf"&gt;every&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;just&lt;/span&gt; &lt;span class="nc"&gt;Runs&lt;/span&gt;

      &lt;span class="c1"&gt;// 2.5 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// 2.6 Execute the method we want to test&lt;/span&gt;
      &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

      &lt;span class="c1"&gt;// 2.7 Verify HelloConsole mock print() method&lt;/span&gt;
      &lt;span class="c1"&gt;// has been called once with "Hello Test!"&lt;/span&gt;
      &lt;span class="nf"&gt;verify&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Task :test

org.hello.HelloAppTest &amp;gt; HelloApp &amp;gt; should print hello message PASSED

org.hello.HelloMessageTest &amp;gt; HelloMessage &amp;gt; should return hello world PASSED

BUILD SUCCESSFUL in 3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>kotlin</category>
      <category>kotest</category>
      <category>mockk</category>
    </item>
    <item>
      <title>Tests Everywhere - JavaScript</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:09:06 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-javascript-4oo0</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-javascript-4oo0</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://developer.mozilla.org/docs/Web/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/javascript#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/javascript#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/javascript/HelloMessage.js" rel="noopener noreferrer"&gt;HelloMessage.js&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/javascript/HelloConsole.js" rel="noopener noreferrer"&gt;HelloConsole.js&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/javascript/HelloApp.js" rel="noopener noreferrer"&gt;HelloApp.js&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;helloMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;helloConsole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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;helloMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;helloMessage&lt;/span&gt;&lt;span class="p"&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;helloConsole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;helloConsole&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&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;helloConsole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&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;helloMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;ol&gt;
&lt;li&gt;Create main function in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/javascript/Main.js" rel="noopener noreferrer"&gt;Main.js&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&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="nx"&gt;HelloMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HelloMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloConsole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HelloConsole&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HelloApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HelloApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://jestjs.io/docs/getting-started" rel="noopener noreferrer"&gt;Jest &amp;gt; Getting Started&lt;/a&gt; guide ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/javascript/HelloMessage.test.js" rel="noopener noreferrer"&gt;HelloMessage.test.js&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HelloMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/javascript/HelloApp.test.js" rel="noopener noreferrer"&gt;HelloApp.test.js&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 2.1 Mock ./HelloMessage module&lt;/span&gt;
&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HelloMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 2.2 Mock ./HelloConsole module&lt;/span&gt;
&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./HelloConsole&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HelloApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should print hello message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messageText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello Test!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.3 Set mock implementation to be returned&lt;/span&gt;
    &lt;span class="c1"&gt;// anytime new HelloMessage() is called&lt;/span&gt;
    &lt;span class="nx"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messageText&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;// 2.4 Create a HelloMessage&lt;/span&gt;
    &lt;span class="c1"&gt;// that will be a mock thanks to 2.1 and 2.2&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.5 Create a HelloConsole&lt;/span&gt;
    &lt;span class="c1"&gt;// that will be a mock thanks to 2.3&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.6 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// 2.7 Execute the method we want to test&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 2.8 Verify HelloConsole mock print() method&lt;/span&gt;
    &lt;span class="c1"&gt;// has been called once with "Hello Test!"&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messageText&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;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; jest

 PASS  ./HelloMessage.test.js
 PASS  ./HelloApp.test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.229 s
Ran all test suites.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>javascript</category>
      <category>jest</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Tests Everywhere - Java</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Thu, 23 Nov 2023 00:00:13 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-java-1c2j</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-java-1c2j</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://openjdk.org/" rel="noopener noreferrer"&gt;Java&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://junit.org/junit5/" rel="noopener noreferrer"&gt;JUnit5&lt;/a&gt;, &lt;a href="https://site.mockito.org/" rel="noopener noreferrer"&gt;Mockito&lt;/a&gt; and &lt;a href="https://assertj.github.io/doc/" rel="noopener noreferrer"&gt;AssertJ&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/java#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/java#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/java/src/main/java/org/hello/HelloApp.java" rel="noopener noreferrer"&gt;HelloMessage.java&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create it as a class as &lt;a href="https://site.mockito.org/" rel="noopener noreferrer"&gt;Mockito&lt;/a&gt; is able to mock final classes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Same way create &lt;code&gt;HelloConsole&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/java/src/main/java/org/hello/HelloConsole.java" rel="noopener noreferrer"&gt;HelloConsole.java&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/java/src/main/java/org/hello/HelloApp.java" rel="noopener noreferrer"&gt;HelloApp.java&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HelloApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloMessage&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;printHello&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create main class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/java/src/main/java/org/hello/Main.java" rel="noopener noreferrer"&gt;Main.java&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printHello&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests" rel="noopener noreferrer"&gt;JUnit5 &amp;gt; Writing Tests&lt;/a&gt;, &lt;a href="https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#stubbing" rel="noopener noreferrer"&gt;Mockito &amp;gt; Stubbing&lt;/a&gt; and &lt;a href="https://assertj.github.io/doc/#assertj-core-assertions-guide" rel="noopener noreferrer"&gt;AssertJ &amp;gt; Core assertions guide&lt;/a&gt; guides ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/java/src/test/java/org/hello/HelloMessageTest.java" rel="noopener noreferrer"&gt;HelloMessageTest.java&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;shouldReturnHelloWorld&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/java/src/test/java/org/hello/HelloAppTest.java" rel="noopener noreferrer"&gt;HelloAppTest.java&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;shouldPrintHelloMessage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;messageText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello Test!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 2.1 Create a mock of HelloMessage&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// 2.2 Return "Hello Test!" whenever getText() is called&lt;/span&gt;
  &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2.2 Create a mock of HelloConsole&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HelloConsole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2.3 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HelloApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// 2.4 Execute the method we want to test&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printHello&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 2.5 Verify HelloConsole mock print() method&lt;/span&gt;
  &lt;span class="c1"&gt;// has been called once with "Hello Test!"&lt;/span&gt;
  &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Task :test

HelloAppTest &amp;gt; shouldPrintHelloMessage() PASSED

HelloMessageTest &amp;gt; shouldReturnHelloWorld() PASSED

BUILD SUCCESSFUL in 2s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>java</category>
      <category>junit5</category>
      <category>mockito</category>
    </item>
    <item>
      <title>Tests Everywhere - Go</title>
      <dc:creator>Roger Viñas Alcon</dc:creator>
      <pubDate>Wed, 22 Nov 2023 23:56:58 +0000</pubDate>
      <link>https://dev.to/rogervinas/tests-everywhere-go-2h3m</link>
      <guid>https://dev.to/rogervinas/tests-everywhere-go-2h3m</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rogervinas" rel="noopener noreferrer"&gt;
        rogervinas
      &lt;/a&gt; / &lt;a href="https://github.com/rogervinas/tests-everywhere" rel="noopener noreferrer"&gt;
        tests-everywhere
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🤠 Tests, Tests Everywhere!
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; testing &lt;a href="https://dev.to/rogervinas/tests-tests-everywhere-1b6h"&gt;this simple Hello World&lt;/a&gt; with &lt;a href="https://pkg.go.dev/testing" rel="noopener noreferrer"&gt;Standard library&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Show me the code

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/go#run-this-project-using--docker" rel="noopener noreferrer"&gt;Run this project using 🐳 docker&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://github.com/rogervinas/tests-everywhere/tree/main/go#run-this-project-locally" rel="noopener noreferrer"&gt;Run this project locally&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloMessage&lt;/code&gt; interface and &lt;code&gt;HelloWorldMessage&lt;/code&gt; implementation in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/go/HelloMessage.go" rel="noopener noreferrer"&gt;HelloMessage.go&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Text&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="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloWorldMessage&lt;/span&gt; &lt;span class="k"&gt;struct&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;message&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HelloWorldMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Text&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="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating it as an interface will allow us to mock it for testing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Same way create &lt;code&gt;HelloConsole&lt;/code&gt; interface and &lt;code&gt;HelloSystemConsole&lt;/code&gt; implementation in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/go/HelloConsole.go" rel="noopener noreferrer"&gt;HelloConsole.go&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloSystemConsole&lt;/span&gt; &lt;span class="k"&gt;struct&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;console&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HelloSystemConsole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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;ol&gt;
&lt;li&gt;Create &lt;code&gt;HelloApp&lt;/code&gt; class in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/go/HelloApp.go" rel="noopener noreferrer"&gt;HelloApp.go&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloApp&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;HelloMessage&lt;/span&gt;
  &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="n"&gt;HelloConsole&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;app&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;PrintHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&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;ol&gt;
&lt;li&gt;Create main function in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/go/Main.go" rel="noopener noreferrer"&gt;Main.go&lt;/a&gt; that wraps it all together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloWorldMessage&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloSystemConsole&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PrintHello&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;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Following &lt;a href="https://pkg.go.dev/testing" rel="noopener noreferrer"&gt;Standard library &amp;gt; testing&lt;/a&gt; guide ...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloMessage&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/go/HelloMessage_test.go" rel="noopener noreferrer"&gt;HelloMessage_test.go&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestShouldReturnHelloWorld&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;messageText&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloWorldMessage&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;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;messageText&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 %s but got %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&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;ol&gt;
&lt;li&gt;Test &lt;code&gt;HelloApp&lt;/code&gt; in &lt;a href="https://github.com/rogervinas/tests-everywhere/blob/main/go/HelloApp_test.go" rel="noopener noreferrer"&gt;HelloApp_test.go&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// 2.1 Define a HelloMessageMock struct that ...&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloMessageMock&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ... fullfils HelloMessage interface&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;message&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HelloMessageMock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Text&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// 2.2 Define a HelloConsoleMock that ...&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HelloConsoleMock&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Calls&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;Text&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// ... fullfills HelloConsole interface&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;console&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HelloConsoleMock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calls&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
  &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&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;TestShouldReturnPrintHelloMessage&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;messageText&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Hello Test!"&lt;/span&gt;

  &lt;span class="c"&gt;// 2.3 Create a HelloMessageMock&lt;/span&gt;
  &lt;span class="c"&gt;// that will return "Hello Test!"&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloMessageMock&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// 2.4 Create a HelloConsoleMock&lt;/span&gt;
  &lt;span class="c"&gt;// that will capture its calls&lt;/span&gt;
  &lt;span class="n"&gt;console&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloConsoleMock&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="c"&gt;// 2.5 Create a HelloApp, the one we want to test, passing the mocks&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HelloApp&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c"&gt;// 2.6 Execute the method we want to test&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PrintHello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c"&gt;// 2.7 Assert HelloConsoleMock has been called once&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calls&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;1&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;"HelloConsole expected calls 1 but got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// 2.8 Assert HelloConsoleMock has been called with "Hello Test!"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;messageText&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;"HelloConsole expected text %s but got %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messageText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&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;ol&gt;
&lt;li&gt;Test output should look like:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== RUN   TestShouldReturnPrintHelloMessage
--- PASS: TestShouldReturnPrintHelloMessage (0.00s)
=== RUN   TestShouldReturnHelloWorld
--- PASS: TestShouldReturnHelloWorld (0.00s)
PASS
ok      org.hello/main  0.412s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy Testing! 💙&lt;/p&gt;

</description>
      <category>test</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
