Jekyll2023-02-02T11:16:08+01:00http://christophd.github.io/feed.xmlChristoph DeppischJava developer, Open Source enthusiastChristoph DeppischGetting started with Camel K using Developer Sandbox2023-02-02T00:00:00+01:002023-02-02T00:00:00+01:00http://christophd.github.io/blog/camel-k-dev-sandbox<p>Have you ever asked yourself what it feels like to use Camel K?
Have you ever wanted to just give it a try and have some hands-on experience?
There is some great news for you!</p>
<p><a href="/assets/images/camel-k-dev-sandbox/featured.png"><img src="/assets/images/camel-k-dev-sandbox/featured.png" alt="Featured" /></a></p>
<p>Camel K is now available on <a href="https://developers.redhat.com/developer-sandbox">Developer Sandbox</a>,
so you can just get your own sandbox instance and try out Camel K within minutes.</p>
<h1 id="what-is-the-openshift-developer-sandbox">What is the OpenShift Developer Sandbox?!</h1>
<p>The <a href="https://developers.redhat.com/developer-sandbox">OpenShift Developer Sandbox</a> gives you 30-days free access to a shared
OpenShift and Kubernetes cluster for development and learning purposes.</p>
<p>The sandbox clusters provide</p>
<ul>
<li><a href="https://developers.redhat.com/developer-sandbox/activities">Guided tutorials</a> to experience and run sample applications</li>
<li><a href="https://developers.redhat.com/node/225251">Prebuilt sample applications</a> to explore different aspects of developing and running applications in the cloud</li>
<li>Access the browser-based Red Hat OpenShift Dev Spaces</li>
<li>(formerly CodeReady Workspaces)</li>
<li>GitHub integration to your own source code</li>
</ul>
<h1 id="get-your-own-sandbox">Get your own sandbox</h1>
<p>To get you started with Developer Sandbox please follow the registration process on <a href="https://developers.redhat.com/developer-sandbox">https://developers.redhat.com/developer-sandbox</a>.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/01_starting_page.png"><img src="/assets/images/camel-k-dev-sandbox/01_starting_page.png" alt="Developer Sandbox" /></a></p>
<p>You will be provided with your own sandbox instance within minutes.
Login to your sandbox and get ready to start using Camel K!</p>
<p><a href="/assets/images/camel-k-dev-sandbox/02_login_page.png"><img src="/assets/images/camel-k-dev-sandbox/02_login_page.png" alt="Developer Sandbox Login" /></a></p>
<h1 id="getting-started-with-camel-k">Getting started with Camel K</h1>
<p>Camel K is available on your sandbox by default.
There is no need to install the operator or to set up any permissions.
You can just start creating your very first Camel K integration.</p>
<h2 id="deploy-your-first-integration">Deploy your first integration</h2>
<p>Start by navigating to the <strong>Topology</strong> view.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/03_topology_view.png"><img src="/assets/images/camel-k-dev-sandbox/03_topology_view.png" alt="Topology View" /></a></p>
<p>Click on the <strong>Start building your application</strong> link right in the middle of the <strong>Topology</strong> view canvas.
You will be provided with a filter input field.
Type <em>“Integration”</em> to search for Camel K integration resources.
Select the Camel K <strong>Integration</strong> resource and hit the <strong>Create</strong> button.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/04_create_integration.png"><img src="/assets/images/camel-k-dev-sandbox/04_create_integration.png" alt="Create Integration" /></a></p>
<p>This opens the YAML form view to create the Camel K integration.
A sample YAML is already provided.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/05_integration_yaml.png"><img src="/assets/images/camel-k-dev-sandbox/05_integration_yaml.png" alt="Create Integration YAML" /></a></p>
<p>For now leave everything as suggested and just use a more meaningful integration name (<em>metadata/name</em>) such as <strong>timer-to-log</strong> and hit <strong>Create</strong>.</p>
<p>In a few moments you will see the Camel K integration called <strong>timer-to-log</strong> up and running in your personal sandbox.
The more experienced Camel users may notice the Camel domains specific language used in the flows section of the integration.
This is where the Camel route logic is happening.
You can use the well-known Camel language syntax such as <code class="language-plaintext highlighter-rouge">from:uri</code> or <code class="language-plaintext highlighter-rouge">set-body</code>.
The sample integration uses a timer component and periodically prints a message (<em>“Hello from Camel K”</em>) to the console output.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/06_running_integration.png"><img src="/assets/images/camel-k-dev-sandbox/06_running_integration.png" alt="Running Integration" /></a></p>
<p>The <strong>Topology</strong> view shows the integration in a running state and also provides some details such as pod logs when selecting the integration.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/07_integration_details.png"><img src="/assets/images/camel-k-dev-sandbox/07_integration_details.png" alt="Integration Details" /></a></p>
<p>Try to view the Pod logs and see how the integration periodically prints <em>“Hello from Camel K”</em> to the console output.
The sample Camel K integration is very simple but it provides a good starting point for exploring more features in Camel and Camel K.
Try to experiment and add more logic to the integration using the YAML Camel domain specific language for instance.</p>
<h1 id="what-about-kamelets">What about Kamelets?!</h1>
<p>Camel K has introduced the concept of Kamelets (<strong>Kamel</strong> route snipp<strong>ets</strong>) which represent ready to use Camel routes written by Camel experts.
Kamelets serve a very specific use case and are designed to simplify its usage.
Usually the user just needs to provide some input properties to have the integration running.</p>
<p>You can choose from a huge set of Kamelets from the <a href="/assets/images/camel-k-dev-sandbox//camel-kamelets/3.20.x/">Apache Kamelet catalog”/></a>.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/kamelet_catalog.png"><img src="/assets/images/camel-k-dev-sandbox/kamelet_catalog.png" alt="Apache Kamelet catalog" /></a></p>
<p>The interesting thing about Kamelets is that they are able to bind to different event driven messaging solutions such as
Apache Kafka or Knative in order to act as an event source or sink.</p>
<h1 id="using-kamelets--knative-on-the-sandbox">Using Kamelets & Knative on the sandbox</h1>
<p>Here is a small demo on how to use Kamelets in your sandbox in combination with Knative eventing (which is also available in your sandbox out-of-the-box).</p>
<h2 id="create-a-knative-channel">Create a Knative channel</h2>
<p>First of all switch back to the Topology view and open the context menu (mouse right-click on the canvas).</p>
<p><a href="/assets/images/camel-k-dev-sandbox/08_create_knative_channel.png"><img src="/assets/images/camel-k-dev-sandbox/08_create_knative_channel.png" alt="Create Knative Channel" /></a></p>
<p>In the context menu select <strong>Channel</strong> to create a new Knative eventing channel.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/09_knative_channel_form.png"><img src="/assets/images/camel-k-dev-sandbox/09_knative_channel_form.png" alt="Knative Channel Form" /></a></p>
<p>Use the defaults in the following form and just give a reasonable name for the channel (<strong>greetings</strong>).
Click on <strong>Create</strong> and see how the channel icon appears in your <strong>Topology</strong> view.</p>
<h2 id="create-custom-greeting-source-kamelet">Create custom greeting-source Kamelet</h2>
<p>The Knative channel needs an event source that publishes events on the channel.
The demo creates a new custom <code class="language-plaintext highlighter-rouge">greeting-source</code> Kamelet acting as such an event source.
Open the context menu once again in your canvas.
This time select the <em>“From Catalog”</em> option.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/10_create_from_catalog.png"><img src="/assets/images/camel-k-dev-sandbox/10_create_from_catalog.png" alt="Create From Catalog" /></a></p>
<p>This opens the developer catalog with all available application options on the sandbox.
Now filter for <em>“Kamelet”</em> and see the Kamelet options.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/11_create_kamelet.png"><img src="/assets/images/camel-k-dev-sandbox/11_create_kamelet.png" alt="Create Kamelet" /></a></p>
<p>Select <strong>Kamelet</strong> and hit <strong>Create</strong>.
This opens the YAML form view for creating a new Kamelet resource in your sandbox.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/12_kamelet_yaml.png"><img src="/assets/images/camel-k-dev-sandbox/12_kamelet_yaml.png" alt="Create Kamelet YAML" /></a></p>
<p><em>Kamelet source</em></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">camel.apache.org/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Kamelet</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">greetings-source</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="s">camel.apache.org/kamelet.support.level</span><span class="pi">:</span> <span class="s">Stable</span>
<span class="s">camel.apache.org/catalog.version</span><span class="pi">:</span> <span class="s">0.10.0</span>
<span class="s">camel.apache.org/kamelet.icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ijk0NCAtNzcxIDI1MDAgMjMzNCI+PHBhdGggZD0iTTIxMTEuNCAyMTAuNWMtMTAzLjYgMTc0LjItMjAyLjkgMzQyLjktMzA0LjIgNTEwLjMtMjYgNDMtMzguOSA3OC0xOC4xIDEzMi42IDU3LjQgMTUwLjktMjMuNiAyOTcuOC0xNzUuOCAzMzcuNi0xNDMuNSAzNy42LTI4My40LTU2LjctMzExLjgtMjEwLjQtMjUuMi0xMzYgODAuMy0yNjkuMyAyMzAuMy0yOTAuNiAxMi42LTEuOCAyNS40LTIgNDYuNS0zLjZsMjI4LjEtMzgyLjVjLTE0My41LTE0Mi43LTIyOC45LTMwOS40LTIxMC01MTYgMTMuNC0xNDYuMSA3MC44LTI3Mi4zIDE3NS44LTM3NS44IDIwMS0xOTguMSA1MDcuOC0yMzAuMiA3NDQuNS03OC4xIDIyNy40IDE0Ni4xIDMzMS41IDQzMC42IDI0Mi44IDY3NC4xLTY2LjktMTguMS0xMzQuMy0zNi40LTIwOC40LTU2LjUgMjcuOS0xMzUuNCA3LjMtMjU3LTg0LjEtMzYxLjEtNjAuMy02OC44LTEzNy44LTEwNC44LTIyNS44LTExOC4xLTE3Ni41LTI2LjctMzQ5LjggODYuNy00MDEuMiAyNTkuOS01OC4zIDE5Ni42IDMwIDM1Ny4yIDI3MS40IDQ3OC4yeiIgZmlsbD0iI2M3M2E2MyIvPjxwYXRoIGQ9Ik0yNDA3LjMgNC41YzczIDEyOC44IDE0Ny4xIDI1OS41IDIyMC42IDM4OSAzNzEuMy0xMTQuOSA2NTEuMiA5MC43IDc1MS43IDMxMC43IDEyMS4zIDI2NS44IDM4LjQgNTgwLjYtMTk5LjkgNzQ0LjYtMjQ0LjUgMTY4LjMtNTUzLjggMTM5LjYtNzcwLjQtNzYuNyA1NS4yLTQ2LjIgMTEwLjctOTIuNiAxNzAtMTQyLjIgMjE0IDEzOC42IDQwMS4xIDEzMi4xIDU0MC4xLTMyLjEgMTE4LjUtMTQwIDExNS45LTM0OC44LTYtNDg1LjgtMTQwLjctMTU4LjItMzI5LjItMTYzLTU1Ny0xMS4yLTk0LjUtMTY3LjctMTkwLjYtMzM0LTI4Mi4yLTUwMi44LTMwLjktNTYuOS02NC45LTg5LjktMTM0LjUtMTAyLTExNi4xLTIwLjEtMTkxLjEtMTE5LjktMTk1LjYtMjMxLjYtNC40LTExMC41IDYwLjctMjEwLjQgMTYyLjQtMjQ5LjMgMTAwLjgtMzguNiAyMTkuMS03LjQgMjg2LjkgNzguMyA1NS40IDcwIDczIDE0OC44IDQzLjkgMjM1LjItOC4xIDI0LjEtMTguNiA0Ny40LTMwIDc1Ljl6IiBmaWxsPSIjNGI0YjRiIi8+PHBhdGggZD0iTTI1ODEuOCAxMDU3LjJoLTQ0N2MtNDIuOCAxNzYuMi0xMzUuNCAzMTguNS0yOTQuOCA0MDguOS0xMjQgNzAuMy0yNTcuNSA5NC4yLTM5OS43IDcxLjItMjYxLjgtNDIuMi00NzUuOS0yNzcuOS00OTQuNy01NDMuMy0yMS4zLTMwMC42IDE4NS4zLTU2Ny44IDQ2MC43LTYyNy45IDE5IDY5LjEgMzguMiAxMzguOCA1Ny4yIDIwNy43LTI1Mi43IDEyOC45LTM0MC4yIDI5MS40LTI2OS40IDQ5NC41IDYyLjMgMTc4LjggMjM5LjEgMjc2LjcgNDMxLjEgMjM4LjggMTk2LjEtMzguNyAyOTUtMjAxLjcgMjgyLjktNDYzLjIgMTg1LjkgMCAzNzItMS45IDU1Ny45LjkgNzIuNiAxLjEgMTI4LjYtNi40IDE4My4zLTcwLjQgOTAtMTA1LjMgMjU1LjgtOTUuOCAzNTIuNyAzLjcgOTkuMSAxMDEuNyA5NC40IDI2NS4yLTEwLjUgMzYyLjYtMTAxLjIgOTMuOS0yNjEgODguOS0zNTUuNy0xMi4zLTE5LjUtMjAuOC0zNC44LTQ1LjUtNTQtNzEuMnoiIGZpbGw9IiM0YTRhNGEiLz48L3N2Zz4="</span>
<span class="s">camel.apache.org/provider</span><span class="pi">:</span> <span class="s">Apache Software Foundation</span>
<span class="s">camel.apache.org/kamelet.group</span><span class="pi">:</span> <span class="s">Webhook</span>
<span class="s">camel.apache.org/version</span><span class="pi">:</span> <span class="s">1.11.0</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="s">camel.apache.org/kamelet.type</span><span class="pi">:</span> <span class="s">source</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">definition</span><span class="pi">:</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">>-</span>
<span class="s">Kamelet provides a Http service creating a greeting event for each incoming request.</span>
<span class="s">The integration accepts requests on the "https://integration-external-url/greetings?name={username}" endpoint.</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Greeting Service</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">object</span>
<span class="na">dependencies</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">camel:platform-http'</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">camel:kamelet'</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">from</span><span class="pi">:</span>
<span class="na">uri</span><span class="pi">:</span> <span class="s1">'</span><span class="s">platform-http:///greetings'</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">set-body</span><span class="pi">:</span>
<span class="na">simple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Hello</span><span class="nv"> </span><span class="s">${header.name}!"</span>
<span class="pi">-</span> <span class="na">to</span><span class="pi">:</span>
<span class="na">uri</span><span class="pi">:</span> <span class="s">log:info</span>
<span class="pi">-</span> <span class="na">to</span><span class="pi">:</span> <span class="s1">'</span><span class="s">kamelet:sink'</span>
</code></pre></div></div>
<p>The new Kamelet source uses a <code class="language-plaintext highlighter-rouge">platform-http</code> Camel component in order to provide some Http service to the user.
Each client request should publish a new event on the Knative eventing channel.</p>
<p>The Kamelet is able to use the full Camel domain specific language (e.g. <code class="language-plaintext highlighter-rouge">set-body</code>) and is saved as a reusable eventing source in your namespace.
Each time we reference this Kamelet the given Camel route logic is called.
This is how Camel experts are able to provide ready to use Camel route snippets covering a specific use case.
Hit the <strong>Create</strong> button and continue with the next step to use this new Kamelet source in a so-called KameletBinding.</p>
<h2 id="create-the-greeting-source-kameletbinding">Create the greeting-source KameletBinding</h2>
<p>As already mentioned the Kamelet itself is vendor agnostic and is able to bind to different messaging solutions (e.g. Kakfa, Knative and more).
In the next step we create such a binding in order to bind the <code class="language-plaintext highlighter-rouge">greeting-source</code> Kamelet to a Knative channel.</p>
<p>Again use the context menu (mouse right-click) in the <strong>Topology</strong> view to create a new resource <em>“From Catalog”</em>.
Once again filter the list of available developer resources for <em>“Kamelet”</em>.
But this time select <strong>KameletBinding</strong> as an option from the list.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/11_create_kamelet.png"><img src="/assets/images/camel-k-dev-sandbox/11_create_kamelet.png" alt="Create Kamelet" /></a></p>
<p>This brings you to the YAML form for creating a new KameletBinding resource.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/13_greeting_http_source_yaml.png"><img src="/assets/images/camel-k-dev-sandbox/13_greeting_http_source_yaml.png" alt="Create Kamelet Binding YAML" /></a></p>
<p><em>greeting-http-source KameletBinding</em></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">camel.apache.org/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">KameletBinding</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="s">trait.camel.apache.org/knative-service.enabled</span><span class="pi">:</span> <span class="s1">'</span><span class="s">true'</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">greeting-http-source</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">camel-k</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">source</span><span class="pi">:</span>
<span class="na">ref</span><span class="pi">:</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">camel.apache.org/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Kamelet</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">greetings-source</span>
<span class="na">sink</span><span class="pi">:</span>
<span class="na">ref</span><span class="pi">:</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">messaging.knative.dev/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Channel</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">greetings</span>
</code></pre></div></div>
<p>The binding references the new custom <code class="language-plaintext highlighter-rouge">greeting-source</code> Kamelet as a source and the Knative channel <code class="language-plaintext highlighter-rouge">greetings</code> as a sink.
Hit the <strong>Create</strong> button and wait for the KameletBinding to appear on the <strong>Topology</strong> view.</p>
<p>You should see the binding connected to the Knative channel named <code class="language-plaintext highlighter-rouge">greetings</code>.
Also, you should see a new Knative service created for the binding.
This is because we have been using a trait annotation (<code class="language-plaintext highlighter-rouge">trait.camel.apache.org/knative-service.enabled: 'true'</code>) on the binding.
As we have been using the <code class="language-plaintext highlighter-rouge">platform-http</code> Camel component in the Kamelet source, the Camel K operator and the Knative serving
operator automatically take care of creating an Http service and a route for you.
Also, the service is capable of auto scaling (including auto-scale to zero) due to Knative serving functionality being applied automatically.</p>
<p>Before we start to open the browser in order to call the Http service we just need to create the event sink,
so we are able to verify that the Knative channel really receives some events.</p>
<h2 id="create-the-greeting-sink-kameletbinding">Create the greeting-sink KameletBinding</h2>
<p>Again we create a KameletBinding this time acting as an event sink.
The sink represents a kind of event display where each event on the Knative <code class="language-plaintext highlighter-rouge">greetings</code> channel is logged to the console.</p>
<p>As shown many times in this post, open the context menu in <strong>Topology</strong> view and navigate to the KameletBinding YAML form.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/14_greeting_log_sink_yaml.png"><img src="/assets/images/camel-k-dev-sandbox/14_greeting_log_sink_yaml.png" alt="Create Kamelet Binding YAML" /></a></p>
<p><em>greeting-log-sink KameletBinding</em></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">camel.apache.org/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">KameletBinding</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="s">trait.camel.apache.org/knative-service.enabled</span><span class="pi">:</span> <span class="s1">'</span><span class="s">true'</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">greeting-log-sink</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">camel-k</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">source</span><span class="pi">:</span>
<span class="na">ref</span><span class="pi">:</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">messaging.knative.dev/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Channel</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">greetings</span>
<span class="na">sink</span><span class="pi">:</span>
<span class="na">ref</span><span class="pi">:</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">camel.apache.org/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Kamelet</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">log-sink</span>
<span class="na">properties</span><span class="pi">:</span>
<span class="na">showHeaders</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<p>The sink binding looks quite similar to the source binding that has been created in the previous step.
But this time the binding uses the Knative <code class="language-plaintext highlighter-rouge">greeting</code> channel as a source and the <code class="language-plaintext highlighter-rouge">log-sink</code> Kamelet as a sink.
Please notice that the <code class="language-plaintext highlighter-rouge">log-sink</code> Kamelet is provided out-of-the-box as this is part of the <a href="https://camel.apache.org/camel-kamelets/3.20.x/">Apache Kamelet catalog</a>.</p>
<p>You can just reference one of these Kamelets and use it in your sandbox.</p>
<p>Hit the <strong>Create</strong> button one last time and wait for the binding to appear.
You will see how the sink binding also connects to the <code class="language-plaintext highlighter-rouge">greeting</code> channel.
In addition to that you will also see a new Knative service being created as part of the binding.
Again the KameletBinding is automatically scaled down to zero when there is no traffic on the <code class="language-plaintext highlighter-rouge">greeting</code> channel.</p>
<h2 id="try-the-example">Try the example</h2>
<p>Now everything is up and running, and you should finally see all components on the <strong>Topology</strong> view.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/15_running_sample.png"><img src="/assets/images/camel-k-dev-sandbox/15_running_sample.png" alt="Running sample" /></a></p>
<p>You can now navigate to the Http greeting service endpoint URL by clicking the little icon on the top right of the Knative
service resource in the <strong>Topology</strong> view.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/16_open_endpoint_url.png"><img src="/assets/images/camel-k-dev-sandbox/16_open_endpoint_url.png" alt="Open Endpoint URL" /></a></p>
<p>This takes you to the Http service endpoint in your browser.
You need to use a specific sub-path (<em>/greetings</em>) and give the username as a query parameter (<em>?name=christoph</em>) when calling the Camel K integration.</p>
<p>Something like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://greeting-http-source-{user-name}-dev.apps.{sandbox-host}.openshiftapps.com/greetings?name=christoph
</code></pre></div></div>
<p>Once you have sent this Http client GET request you should see all the example components
(Kamelet <code class="language-plaintext highlighter-rouge">greeting-source</code> => Knative <code class="language-plaintext highlighter-rouge">greeting</code> channel => Kamelet <code class="language-plaintext highlighter-rouge">greeting-sink</code>) working together.
As a result you should be able to verify the <em>“Hello {username}!”</em> message body appearing in the <code class="language-plaintext highlighter-rouge">greeting-log-sink</code> logs (e.g. <em>“Body: Hello christoph!”</em>).</p>
<p><a href="/assets/images/camel-k-dev-sandbox/17_greeting_sink_logs.png"><img src="/assets/images/camel-k-dev-sandbox/17_greeting_sink_logs.png" alt="Greeting Sink Logs" /></a></p>
<p>This completes the more complex example using Kamelets and Knative in your sandbox.</p>
<h1 id="using-the-kamel-cli">Using the kamel CLI</h1>
<p>Up to now we have been using the OpenShift UI to create Camel K resources with YAML.
The <strong>kamel</strong> command line interface provides some more user-friendly alternative,
because you just need to write the Camel routes in your favorite domain specific language (e.g. Groovy, Java, XML, YAML).</p>
<p>You can download the kamel CLI from various sources. Please refer to the documentation for more details on <a href="https://camel.apache.orgcamel-k/1.11.x/cli/cli.html">how to install kamel</a>.
You will also need the OpenShift command line client or the respective Kubernetes CLI for connecting to your individual Developer Sandbox instance.</p>
<p>Kamel needs to connect to your sandbox environment so please copy the login token provided with your sandbox account.</p>
<p><a href="/assets/images/camel-k-dev-sandbox/oc_login.png"><img src="/assets/images/camel-k-dev-sandbox/oc_login.png" alt="OC Login" /></a></p>
<p>The <strong>Copy login command</strong> option takes you to a page where you can copy the individual <code class="language-plaintext highlighter-rouge">oc login</code> command token.
Once your local oc client is connected to the sandbox environment you can also use kamel to connect to your sandbox account.</p>
<p>Now you can use one of the provided Camel K examples and run the integrations via <strong>kamel run</strong> command.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kamel run simple-route.groovy
</code></pre></div></div>
<h1 id="summary">Summary</h1>
<p>The blog post has described the steps to get you started with Camel K on a personal Developer Sandbox instance.
Everything is installed out-of-the-box, so you can just start to write Camel K integrations and Kamelets.
The post also showed a more complex example with Kamelets and Knative event-driven messaging working hand in hand in the sandbox environment.</p>
<p>Please feel free to explore more <a href="https://github.com/apache/camel-k-examples">Camel K examples</a> on the Developer Sandbox and
do not hesitate to give feedback on the user experience.</p>Christoph DeppischHave you ever asked yourself what it feels like to use Camel K? Have you ever wanted to just give it a try and have some hands-on experience? There is some great news for you!Testing Camel K with YAKS2023-01-30T00:00:00+01:002023-01-30T00:00:00+01:00http://christophd.github.io/blog/camel-k-yaks<p>This post describes the steps to test a Camel K integration with YAKS both locally and on the Kubernetes platform.</p>
<p><a href="/assets/images/camel-k-yaks/featured.png"><img src="/assets/images/camel-k-yaks/featured.png" alt="Featured" /></a></p>
<h1 id="what-is-yaks">What is YAKS?</h1>
<p><a href="https://github.com/citrusframework/yaks">YAKS</a> is an Open Source test automation platform that leverages Behavior Driven
Development concepts for running tests locally and on Cloud infrastructure (e.g. <a href="https://kubernetes.io/">Kubernetes</a> or
<a href="https://www.openshift.com/">OpenShift</a>).
This means that the testing tool is able to run your tests both as local tests and natively on Kubernetes.
The framework is specifically designed to verify Serverless and Microservice applications and aims for integration testing
with the application under test up and running in a production-like environment.
A typical YAKS test uses the very same infrastructure as the application under test and exchanges data/events over different
messaging transports (e.g. Http REST, Knative eventing, Kafka, JMS and many more).</p>
<p>As YAKS itself is written in Java the runtime uses a Java virtual machine with build tools such as Maven and integrates with
well known Java testing frameworks such as <a href="https://junit.org/junit5/">JUnit</a>, <a href="https://cucumber.io/">Cucumber</a> and
<a href="https://citrusframework.org">Citrus</a> to run the tests.</p>
<h1 id="understanding-the-camel-k-example">Understanding the Camel K example</h1>
<p>First of all here is a small sample Camel K integration that we would like to test in the following.
The integration exposes a Http service to the user.
The service accepts client Http POST requests that add fruit model objects.
The Camel K route applies content based routing to store the fruits in different AWS S3 buckets.</p>
<p><a href="/assets/images/camel-k-yaks/test-scenario.png"><img src="/assets/images/camel-k-yaks/test-scenario.png" alt="Test Scenario" /></a></p>
<p>In the test scenario YAKS is going to invoke the Camel K service and verify that the message content has been sent to the right AWS S3 bucket.</p>
<p>Here is a sample fruit model object that is subject to be stored in AWS S3:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1000</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Pineapple"</span><span class="p">,</span><span class="w">
</span><span class="nl">"category"</span><span class="p">:{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="s2">"tropical"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"nutrition"</span><span class="p">:{</span><span class="w">
</span><span class="nl">"calories"</span><span class="p">:</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w">
</span><span class="nl">"sugar"</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AVAILABLE"</span><span class="p">,</span><span class="w">
</span><span class="nl">"price"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.59</span><span class="p">,</span><span class="w">
</span><span class="nl">"tags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"sweet"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Here is the Camel K integration route:</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">from</span><span class="o">(</span><span class="s1">'platform-http:/fruits'</span><span class="o">)</span>
<span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s1">'received fruit ${body}'</span><span class="o">)</span>
<span class="o">.</span><span class="na">unmarshal</span><span class="o">().</span><span class="na">json</span><span class="o">()</span>
<span class="o">.</span><span class="na">removeHeaders</span><span class="o">(</span><span class="s2">"*"</span><span class="o">)</span>
<span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s2">"CamelAwsS3Key"</span><span class="o">,</span> <span class="n">constant</span><span class="o">(</span><span class="s2">"fruit.json"</span><span class="o">))</span>
<span class="o">.</span><span class="na">choice</span><span class="o">()</span>
<span class="o">.</span><span class="na">when</span><span class="o">().</span><span class="na">simple</span><span class="o">(</span><span class="s1">'${body[nutrition][sugar]} <= 5'</span><span class="o">)</span>
<span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s2">"CamelAwsS3BucketName"</span><span class="o">,</span> <span class="n">constant</span><span class="o">(</span><span class="s2">"low-sugar"</span><span class="o">))</span>
<span class="o">.</span><span class="na">when</span><span class="o">().</span><span class="na">simple</span><span class="o">(</span><span class="s1">'${body[nutrition][sugar]} > 5 && ${body[nutrition][sugar]} <= 10'</span><span class="o">)</span>
<span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s2">"CamelAwsS3BucketName"</span><span class="o">,</span> <span class="n">constant</span><span class="o">(</span><span class="s2">"medium-sugar"</span><span class="o">))</span>
<span class="o">.</span><span class="na">otherwise</span><span class="o">()</span>
<span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s2">"CamelAwsS3BucketName"</span><span class="o">,</span> <span class="n">constant</span><span class="o">(</span><span class="s2">"high-sugar"</span><span class="o">))</span>
<span class="o">.</span><span class="na">end</span><span class="o">()</span>
<span class="o">.</span><span class="na">marshal</span><span class="o">().</span><span class="na">json</span><span class="o">()</span>
<span class="o">.</span><span class="na">log</span><span class="o">(</span><span class="s1">'sending ${body}'</span><span class="o">)</span>
<span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="s2">"aws2-s3://noop?$parameters"</span><span class="o">)</span>
</code></pre></div></div>
<p>The route uses content based routing EAP based on the nutrition sugar rating of a given fruit in order to send the fruits
to different AWS S3 buckets (low-sugar, medium-sugar, high-sugar).</p>
<p>In the following the test case for this integration needs to invoke the exposed service with different fruits and verify its outcome on AWS S3.</p>
<h1 id="how-to-test-locally-with-yaks">How to test locally with YAKS</h1>
<p>In the beginning let’s just write the test and run it locally.
For now, we do not care how to deploy the application under test in the Cloud infrastructure as everything is running on
the local machine using <a href="https://www.jbang.dev/">JBang</a>.</p>
<p>JBang is a fantastic way to just start coding and running Java code and also Camel K integrations.</p>
<p>YAKS as a framework brings a set of ready-to-use domain specific languages (XML, YAML, Groovy, BDD Cucumber steps) for writing
tests in order to verify your deployed services.</p>
<p>This post uses the Behavior Driven Development integration via Cucumber.
So the YAKS test is a single feature file that uses BDD Gherkin syntax like this:</p>
<div class="language-gherkin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">Feature</span><span class="p">:</span> Camel K Fruit Store
<span class="kn">Background</span><span class="p">:</span>
<span class="err">Given URL</span><span class="p">:</span> <span class="err">http</span><span class="p">:</span><span class="err">//localhost</span><span class="p">:</span><span class="err">8080</span>
<span class="kn">Scenario</span><span class="p">:</span> Create infrastructure
<span class="c"># Start AWS S3 container</span>
<span class="nf">Given </span>Enable service S3
<span class="nf">Given </span>start LocalStack container
<span class="c"># Create Camel K integration</span>
<span class="nf">Given </span>Camel K integration property file aws-s3-credentials.properties
<span class="nf">When </span>load Camel K integration fruit-service.groovy
<span class="err">Then Camel K integration fruit-service should print Started route1 (platform-http</span><span class="p">:</span><span class="err">///fruits)</span>
<span class="kn">Scenario</span><span class="p">:</span> Verify fruit service
<span class="c"># Invoke Camel K service</span>
<span class="err">Given HTTP request body</span><span class="p">:</span> <span class="err">yaks</span><span class="p">:</span><span class="err">readFile('pineapple.json')</span>
<span class="nf">And </span>HTTP request header Content-Type="application/json"
<span class="nf">When </span>send POST /fruits
<span class="nf">Then </span>receive HTTP 200 OK
<span class="c"># Verify uploaded S3 file</span>
<span class="nf">Given </span>New global Camel context
<span class="nf">Given </span>load to Camel registry amazonS3Client.groovy
<span class="nf">Given </span>Camel exchange message header CamelAwsS3Key="fruit.json"
<span class="err">Given receive Camel exchange from("aws2-s3</span><span class="p">:</span><span class="err">//medium-sugar?amazonS3Client=#amazonS3Client&deleteAfterRead=true") with body</span><span class="p">:</span> <span class="err">yaks</span><span class="p">:</span><span class="err">readFile('pineapple.json')</span>
</code></pre></div></div>
<p>Let’s walk through the test step by step.
First of all the feature file uses the usual Given-When-Then BDD syntax to give context, describe the actions and verify the outcome.
Each step calls a specific YAKS action that is provided out of the box by the framework.
The user is able to choose from a <a href="https://github.com/citrusframework/yaks/tree/main/java/steps">huge set of steps</a> that
automatically perform actions like sending/receiving Http requests/responses, starting <a href="https://www.testcontainers.org/">Testcontainers</a>,
running Camel routes, connecting to a database, publishing events on Kafka or Knative brokers and many more.</p>
<p>In the first scenario the test automatically prepares some required infrastructure.
The YAKS test starts a Localstack <a href="https://www.testcontainers.org/">Testcontainer</a> to have an AWS S3 test instance running (<code class="language-plaintext highlighter-rouge">Given start LocalStack container</code>).
Then the test loads and starts the Camel K integration under test (<code class="language-plaintext highlighter-rouge">When load Camel K integration fruit-service.groovy</code>) and waits for it to properly start.
In local testing this step starts the Camel K integration using JBang.
Later the post will also run the test in a Kubernetes environment.</p>
<p>Now the infrastructure is up and running and the test is able to load the fruit model object as Http request body
(<code class="language-plaintext highlighter-rouge">Given HTTP request body: yaks:readFile('pineapple.json')</code>) and invoke the Camel K service (<code class="language-plaintext highlighter-rouge">When send POST /fruits</code>).
The test waits for the Http response and verifies its 200 OK status.</p>
<p>In the last step the test verifies that the fruit object has been added to the right AWS S3 bucket (medium-sugar).
As YAKS itself is not able to connect to AWS S3 the test uses Apache Camel for this step.
The test creates a Camel context, loads a AWS client and connects to AWS S3 with a temporary Camel route
(<code class="language-plaintext highlighter-rouge">Given receive Camel exchange from("aws2-s3://medium-sugar?amazonS3Client=#amazonS3Client&deleteAfterRead=true")</code>).
With this Apache Camel integration YAKS is able to use the complete 300+ Camel components for sending and receiving messages
to various messaging transports. The Camel exchange body should be the same fruit model object (<code class="language-plaintext highlighter-rouge">yaks:readFile('pineapple.json'</code>)
as posted in the initial Http request.</p>
<p>YAKS uses the powerful message payload validation capabilities provided by Citrus for this message content verification.
The validation is able to compare message contents of type XML, Json, plaintext and many more.</p>
<p>This completes the test case. You can now run this test with Cucumber and JUnit for instance.
The easiest way though to directly run tests with YAKS is to use the <a href="https://github.com/citrusframework/yaks/releases">YAKS command line client</a>.
You do not need to set up a whole project with Maven dependencies and so on.
Just write the test file and run with:</p>
<p>```shell script
$ yaks run fruit-service.feature –local</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
You should see some log output like this:
</code></pre></div></div>
<p>INFO |
INFO | ————————————————————————
INFO | .__ __
INFO | <strong>__ |</strong>|/ |<strong>__</strong>__ __ __ <strong>__</strong>
INFO | _/ <strong><em>| \ ___ __ \ | \/ __</em>/
INFO | \ _</strong>| || | | | \/ | /___ <br />
INFO | ___ ><strong>||</strong>| |<strong>| |</strong><strong>//</strong>__ >
INFO | \/ \/
INFO |
INFO | C I T R U S T E S T S 3.4.0
INFO |
INFO | ————————————————————————
INFO |</p>
<p>Scenario: Create infrastructure # fruit-service.feature:6
Given URL: http://localhost:8080
Given Enable service S3
[…]</p>
<p>Scenario: Verify fruit service # fruit-service.feature:20
Given URL: http://localhost:8080
Given HTTP request body: yaks:readFile(‘pineapple.json’)
[…]</p>
<p>Scenario: Remove infrastructure # fruit-service.feature:31
Given URL: http://localhost:8080
Given delete Camel K integration fruit-service
Given stop LocalStack container</p>
<p>3 Scenarios (3 passed)
18 Steps (18 passed)
0m18,051s</p>
<table>
<tbody>
<tr>
<td>INFO</td>
<td>————————————————————————</td>
</tr>
<tr>
<td>INFO</td>
<td> </td>
</tr>
<tr>
<td>INFO</td>
<td>CITRUS TEST RESULTS</td>
</tr>
<tr>
<td>INFO</td>
<td> </td>
</tr>
<tr>
<td>INFO</td>
<td>Create infrastructure …………………………………… SUCCESS</td>
</tr>
<tr>
<td>INFO</td>
<td>Verify fruit service ……………………………………. SUCCESS</td>
</tr>
<tr>
<td>INFO</td>
<td>Remove infrastructure …………………………………… SUCCESS</td>
</tr>
<tr>
<td>INFO</td>
<td> </td>
</tr>
<tr>
<td>INFO</td>
<td>TOTAL: 3</td>
</tr>
<tr>
<td>INFO</td>
<td>FAILED: 0 (0.0%)</td>
</tr>
<tr>
<td>INFO</td>
<td>SUCCESS: 3 (100.0%)</td>
</tr>
<tr>
<td>INFO</td>
<td> </td>
</tr>
<tr>
<td>INFO</td>
<td>————————————————————————</td>
</tr>
</tbody>
</table>
<p>3 Scenarios (3 passed)
18 Steps (18 passed)
0m18,051s</p>
<p>Test results: Total: 0, Passed: 1, Failed: 0, Errors: 0, Skipped: 0
fruit-service (fruit-service.feature): Passed</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
# Running YAKS in the Cloud
YAKS is able to run tests both locally and as part of a Kubernetes cluster.
When running tests on Cloud infrastructure YAKS leverages the Operator SDK and provides a specific operator to manage the
test case resources on the cluster.
Each time you declare a test in the form of a [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
the YAKS operator automatically takes care of preparing the proper runtime in order to execute the test as a Kubernetes Pod.
Why would you want to run tests as Cloud-native resources on the Kubernetes platform? Kubernetes has become a standard target
platform for Serverless and Microservices architectures.
Developing the services is different in many aspects compared to what we have done for decades.
Writing a Serverless or Microservices application for instance with Camel K is very declarative.
As a developer you just write the Camel route and run it as an integration via the Camel K operator directly on the cluster.
The declarative approach as well as the nature of Serverless applications make us rely on a given runtime infrastructure,
and it is essential to verify the applications also on that infrastructure. So it is only natural to also move the verifying
tests into this very same Cloud infrastructure.
This is why YAKS also brings your tests to the Cloud infrastructure for integration and end-to-end testing.
So here is how it works. You are able to run the very same YAKS test that has been run locally also as a Pod in Kubernetes.
YAKS provides a Kubernetes operator and a set of CRDs (custom resources) that we need to install on the cluster.
The best way to install YAKS is to use the [OperatorHub](https://operatorhub.io/operator/yaks) or the yaks CLI tools that
you can download from the [YAKS GitHub release pages](https://github.com/citrusframework/yaks/releases).
With the yaks-client binary simply run this install command:
```shell script
$ yaks install
</code></pre></div></div>
<p>This command prepares your Kubernetes cluster for running tests with YAKS.
It will take care of installing the YAKS custom resource definitions, setting up role permissions and creating the YAKS operator
in a global operator namespace.</p>
<p><strong>Important:</strong> You need to be a cluster admin to install custom resource definitions.
The operation needs to be done only once for the entire cluster.</p>
<p>Now that the YAKS operator is up and running you can run the very same test from local testing also on the Cloud infrastructure.
The only thing that needs to be done is to adjust the Http endpoint URL of the Camel K integration from <code class="language-plaintext highlighter-rouge">http://localhost:8080</code> to <code class="language-plaintext highlighter-rouge">http://fruit-service.${YAKS_NAMESPACE</code>}</p>
<p>```shell script
$ yaks run fruit-service.feature</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Please notice that we have just skipped the `--local` CLI option.
Instead of using local JBang tooling to run the test locally now the YAKS CLI connects to the Kubernetes cluster in order
to create the test as a custom resource.
From there the YAKS operator takes over preparing the test runtime and running the test as a Pod.
But wait! The test did prepare some infrastructure, in particular the Camel K integration and the AWS S3 Localstack Testcontainer instance.
How does that work inside Kubernetes? YAKS completely takes care of it.
The Camel K integration is run with the Camel K operator running on the same Kubernetes cluster.
And the Testcontainer AWS S3 instance is automatically run as a Pod in Kubernetes.
Even connection settings are handled automatically. It just works!
You will see some similar test log output when running the test remotely and the test performs its actions and its validation
exactly the same as locally.
You can also review the test Pod outcome with:
```shell script
$ yaks ls
</code></pre></div></div>
<p>This is an example output you should get:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME PHASE TOTAL PASSED FAILED SKIPPED ERRORS
fruit-service Passed 3 3 0 0 0
</code></pre></div></div>
<h1 id="demo">Demo</h1>
<p>The whole demo code is available on this <a href="https://github.com/christophd/yaks-demo-camel-k">GitHub repository</a>.
It also shows how to integrate the tests in a <a href="https://github.com/christophd/yaks-demo-camel-k/actions">GitHub CI actions workflow</a>,
so you can run the tests automatically with every code change.</p>
<h1 id="conclusion">Conclusion</h1>
<p>This blog post showed how you can test Camel K integrations locally and in a Cloud infrastructure with YAKS.
YAKS as a framework has many more features to offer (Kafka, Knative, OpenAPI, etc.) so this is just the start of a new testing
platform for BDD testing in Cloud-native environments!</p>
<p>Please give feedback, ideas and of course contributions to this.
Feel free to add your thoughts on the YAKS repository by opening <a href="https://github.com/citrusframework/yaks/issues">new issues</a> or
even share your appreciation with a <a href="https://github.com/citrusframework/yaks">star on GitHub</a>.</p>Christoph DeppischThis post describes the steps to test a Camel K integration with YAKS both locally and on the Kubernetes platform.Managing Kamelet event sources with kn2021-09-30T00:00:00+02:002021-09-30T00:00:00+02:00http://christophd.github.io/blog/managing-kamelets-with-kn<p>The latest community version of the <a href="https://github.com/knative/client">Knative client</a> v0.26 includes a new kn plugin for managing Kamelets as Knative event sources
(GitHub: <a href="https://github.com/knative-sandbox/kn-plugin-source-kamelet">knative-sandbox/kamelet-plugin-source-kamelet</a>).
With the new plugin users of the kn tooling can directly list the available Kamelet sources and bind these Kamelets to Knative resources such as brokers, channels or services.</p>
<p>The Kamelets facilitate a whole new world of event source possibilities allowing users to connect to external services (AWS, Twitter, Telegram, Postgres) as part of Knative eventing.</p>
<h2 id="kamelets---what-is-this">Kamelets - what is this?</h2>
<p>Kamelets (Kamel route snippets) introduce a new concept in <a href="https://github.com/apache/camel">Apache Camel</a> that allows users to connect to external systems via a simplified ready-to-use connectors,
hiding all the low level details about how those connections are implemented.</p>
<p>The user only provides a set of properties that the Kamelet needs for connecting to a foreign service (e.g. authorization token, target destination, connection credentials).
The heavy lift of establishing the connection and exchanging data is done with the Camel components that run as part of the Kamelet runtime.</p>
<p>If you want to use Kamelets on your Kubernetes cluster simply install the Camel K operator (<a href="https://camel.apache.org/camel-k/latest/installation/installation.html">installation guide</a>).</p>
<p>The user can then choose from a constantly growing <a href="https://camel.apache.org/camel-kamelets/latest/index.html">Kamelet catalog</a> that provides a great ensemble of ready-to-use Kamelets.</p>
<p>Kamelets are able to act as an event source or sink and complement with Knative eventing and the Cloud events standard.</p>
<h2 id="install-the-kn-kamelet-source-plugin">Install the kn Kamelet source plugin</h2>
<p>Assuming you have the <a href="https://github.com/knative/client/blob/main/docs/README.md">kn CLI tooling</a> installed and ready on your machine you can simply
load the <a href="https://github.com/knative-sandbox/kn-plugin-source-kamelet/releases">Kamelet source plugin binaries</a> in order to add the commands for managing Kamelets as event sources.</p>
<p>By default, the kn tooling knows a set of different event sources and its respective commands to manage those sources.
You can display the command possibilities with the help option.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kn <span class="nb">source</span> <span class="nt">-h</span>
Manage event sources
Usage:
kn <span class="nb">source </span>SOURCE|COMMAND <span class="o">[</span>options]
Aliases:
<span class="nb">source</span>, sources
Available Commands:
apiserver Manage Kubernetes api-server sources
binding Manage sink bindings
container Manage container sources
list List event sources
list-types List event <span class="nb">source </span>types
ping Manage ping sources
Use <span class="s2">"kn <command> --help"</span> <span class="k">for </span>more information about a given command.
Use <span class="s2">"kn options"</span> <span class="k">for </span>a list of global command-line options <span class="o">(</span>applies to all commands<span class="o">)</span><span class="nb">.</span>
</code></pre></div></div>
<p>Now it is the time to load the Kamelet source plugin binary for your operating system from the <a href="https://github.com/knative-sandbox/kn-plugin-source-kamelet/releases">plugin release pages</a>.</p>
<p>Once you have the binary available on your machine you can hook this into your local kn tooling quite easily.
The kn-client project provides a clever plugin architecture for adding commands.
The respective plugin configuration is located in your home directory under <code class="language-plaintext highlighter-rouge">~/.config/kn/plugins</code>.</p>
<p>Save the <code class="language-plaintext highlighter-rouge">kn-source-kamelet</code> binary into this configuration folder or add a symbolic link pointing to the plugin binary there.
You can add the symbolic link as follows:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/.config/kn/plugins
<span class="nb">ln</span> <span class="nt">-s</span> /the/path/to/my/kn-source-kamelet kn-source-kamelet
</code></pre></div></div>
<p>You can then verify the plugin setup by displaying the kn help page once more:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kn <span class="nb">source</span> <span class="nt">-h</span>
Manage event sources
Usage:
kn <span class="nb">source </span>SOURCE|COMMAND <span class="o">[</span>options]
Aliases:
<span class="nb">source</span>, sources
Available Commands:
apiserver Manage Kubernetes api-server sources
binding Manage sink bindings
container Manage container sources
list List event sources
list-types List event <span class="nb">source </span>types
ping Manage ping sources
Plugins:
kamelet ~/.config/kn/plugins/kn-source-kamelet
Use <span class="s2">"kn <command> --help"</span> <span class="k">for </span>more information about a given command.
Use <span class="s2">"kn options"</span> <span class="k">for </span>a list of global command-line options <span class="o">(</span>applies to all commands<span class="o">)</span><span class="nb">.</span>
</code></pre></div></div>
<p>You should see a new plugins section with the Kamelet source plugin listed.
This means that you are now ready to use the plugin commands directly with the kn CLI.</p>
<h2 id="list-kamelets">List Kamelets</h2>
<p>First thing you can do with the plugin is to list all available Kamelets.
This will print a list of Kamelets coming from the <a href="https://camel.apache.org/camel-kamelets/latest/index.html">Kamelet catalog</a> that is installed on your cluster via the Camel K operator.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kn <span class="nb">source </span>kamelet list
NAME PHASE AGE CONDITIONS READY REASON
aws-ddb-streams-source Ready 2d7h 1 OK / 1 True
aws-kinesis-source Ready 2d7h 1 OK / 1 True
aws-s3-source Ready 2d7h 1 OK / 1 True
aws-sqs-source Ready 2d7h 1 OK / 1 True
azure-cosmosdb-source Ready 2d7h 1 OK / 1 True
azure-eventhubs-source Ready 2d7h 1 OK / 1 True
azure-storage-blob-source Ready 2d7h 1 OK / 1 True
azure-storage-queue-source Ready 2d7h 1 OK / 1 True
bitcoin-source Ready 2d7h 1 OK / 1 True
cassandra-source Ready 2d7h 1 OK / 1 True
chuck-norris-source Ready 2d7h 1 OK / 1 True
cron-source Ready 2d7h 1 OK / 1 True
dropbox-source Ready 2d7h 1 OK / 1 True
earthquake-source Ready 2d7h 1 OK / 1 True
elasticsearch-search-source Ready 2d7h 1 OK / 1 True
fhir-source Ready 2d7h 1 OK / 1 True
file-watch-source Ready 2d7h 1 OK / 1 True
ftp-source Ready 2d7h 1 OK / 1 True
ftps-source Ready 2d7h 1 OK / 1 True
github-source Ready 2d7h 1 OK / 1 True
google-calendar-source Ready 2d7h 1 OK / 1 True
google-mail-source Ready 2d7h 1 OK / 1 True
google-sheets-source Ready 2d7h 1 OK / 1 True
http-secured-source Ready 2d7h 1 OK / 1 True
http-source Ready 2d7h 1 OK / 1 True
...
</code></pre></div></div>
<p>This command lists all Kamelets provided by the awesome Camel community.
As you can see the list is huge and it is growing constantly.
All these Kamelets are potential sources for your Knative eventing data streams.</p>
<h2 id="describe-a-kamelet">Describe a Kamelet</h2>
<p>You are able to display details for a Kamelet with the <code class="language-plaintext highlighter-rouge">kamelet describe</code> command:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kn <span class="nb">source </span>kamelet describe github-source
Name: github-source
Namespace: openshift-operators
Labels: camel.apache.org/kamelet.bundled<span class="o">=</span><span class="nb">true</span>,
camel.apache.org/kamelet.readonly<span class="o">=</span><span class="nb">true</span>, camel.apa ...
Annotations: camel.apache.org/catalog.version<span class="o">=</span>0.4.0,
camel.apache.org/kamelet.group<span class="o">=</span>GitHub, came ...
Age: 2d
Description: Github Source - Receive events From Github.
Provider: Apache Software Foundation
Support Level: Preview
Phase: Ready
Properties:
Name Req Type Description
oauthToken ✓ string Oauth token
repoName ✓ string The Github Repository name
repoOwner ✓ string The repository owner
<span class="nb">type</span> ✓ string The <span class="nb">type </span>of event to consume.
Conditions:
OK TYPE AGE REASON
++ Ready 2d
</code></pre></div></div>
<p>The command prints detailed information about the Kamelet source.
In particular the properties that a user needs to set when using the Kamelet in a binding.</p>
<h2 id="create-kamelet-bindings">Create Kamelet bindings</h2>
<p>A Kamelet is able to bind to a Knative resource such as a broker, a channel or a service.
The binding will use the Kamelet as an event source and stream external data into Knative eventing.</p>
<p>You can use the kn client to create such a binding as the kn Kamelet source plugin provides a binding command with subcommands such as create and describe.
See the following example that links the github-source Kamelet to a Knative channel.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kn <span class="nb">source </span>kamelet binding create github-pull-requests <span class="se">\</span>
<span class="nt">--kamelet</span> github-source <span class="se">\</span>
<span class="nt">--channel</span> github-prs <span class="se">\</span>
<span class="nt">--property</span> <span class="nv">oauthToken</span><span class="o">=</span>xxx <span class="se">\</span>
<span class="nt">--property</span> <span class="nv">repoName</span><span class="o">=</span>camel <span class="se">\</span>
<span class="nt">--property</span> <span class="nv">repoOwner</span><span class="o">=</span>apache <span class="se">\</span>
<span class="nt">--property</span> <span class="nb">type</span><span class="o">=</span>pullRequest
</code></pre></div></div>
<p>In the following each pull request on the given GitHub repository creates a new event on the channel <code class="language-plaintext highlighter-rouge">github-prs</code>.</p>
<p>You should now see the created binding when running the <code class="language-plaintext highlighter-rouge">binding list</code> command</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kn <span class="nb">source </span>kamelet binding list
NAME PHASE AGE CONDITIONS READY REASON
github-pull-requests Ready 36s 1 OK / 1 True
</code></pre></div></div>
<h2 id="wrap-up">Wrap up</h2>
<p>This completes the capabilities with the new kn source plugin for managing Kamelets as event sources.
Please stay tuned as this is only the beginning and there are many more plugin features yet to come!
Please give it a try and tell us what you think!</p>Christoph DeppischThe latest community version of the Knative client v0.26 includes a new kn plugin for managing Kamelets as Knative event sources (GitHub: knative-sandbox/kamelet-plugin-source-kamelet). With the new plugin users of the kn tooling can directly list the available Kamelet sources and bind these Kamelets to Knative resources such as brokers, channels or services.Introducing YAKS2020-03-20T00:00:00+01:002020-03-20T00:00:00+01:00http://christophd.github.io/blog/introducing-yaks<p>YAKS is Cloud-Native BDD testing or simply “Yet Another Kubernetes Service”.</p>
<h1 id="what-is-yaks">What is “YAKS”?</h1>
<p>YAKS is an Open Source test automation platform to run your tests as Cloud-Native resources. This means it runs natively on
<a href="https://kubernetes.io/">Kubernetes</a> and <a href="https://www.openshift.com/">OpenShift</a> and is specifically designed to verify your serverless and Microservice components.</p>
<p>The tests that you write with YAKS follow the BDD (Behavior Driven Development) concepts so you can run
<a href="https://cucumber.io/docs/gherkin/reference/">Gherkin</a> (Given-When-Then) feature files as Pod in your cluster.</p>
<p>YAKS provides a specific operator that leverages the Operator SDK to perform operations on Kubernetes resources. Each time you provide
a test as a <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">custom resource</a> the operator takes care on
preparing a JVM test runtime and executes the test as a Pod in the cluster.</p>
<p>The test framework also brings a set of ready-to-use <a href="https://cucumber.io/">Cucumber</a> steps so you can just start writing
your feature files and run them to verify your deployed services.</p>
<h2 id="why-would-you-want-to-do-that">Why would you want to do that?</h2>
<p>Why would you want to run tests as Cloud-Native resources in Kubernetes? Both Kubernetes and OpenShift have become standard target platforms for serverless
and microservices architectures. Developing those architectures is different compared to what we have done for decades before that.</p>
<p>Writing a serverless application for instance with <a href="https://camel.apache.org/projects/camel-k/">Camel K</a> is very declarative. As a developer you write a single
Camel route and run this via the Camel K operator in the Kubernetes cluster. In this approach the unit testing part that we have been used to over so many years
gets more and more transformed into pure integration testing and end-to-end testing. Given by the nature of serverless architectures we rely on a given runtime infrastructure
and it is hard to run tests outside of that infrastructure.</p>
<p>Let us have a look at a sample Camel K integration:</p>
<p>Sample: <em>Camel K integration.groovy</em></p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="c1">// expose a rest endpoint that routes messages to a Kafka topic</span>
<span class="n">rest</span><span class="o">().</span><span class="na">post</span><span class="o">(</span><span class="s2">"/resources"</span><span class="o">)</span>
<span class="o">.</span><span class="na">route</span><span class="o">()</span>
<span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="s2">"kafka:messages"</span><span class="o">)</span>
<span class="c1">// transform all messages and publish them on a HTTP endpoint</span>
<span class="n">from</span><span class="o">(</span><span class="s2">"kafka:messages"</span><span class="o">)</span>
<span class="o">.</span><span class="na">transform</span><span class="o">()...</span> <span class="c1">// any kind of transformation</span>
<span class="o">.</span><span class="na">to</span><span class="o">(</span><span class="s2">"http://myendpoint/messages"</span><span class="o">)</span></code></pre></figure>
<p>While we develop this integration we always run the Camel K integration source within the target infrastructure including its messaging transports, databases and other services. It is only natural
to also move the verifying tests into this very same infrastructure.</p>
<p>This way the tests can make use of all Kubernetes services including access to internal services, too. Also the tests are able to simulate 3rd party services
(like the <code class="language-plaintext highlighter-rouge">http://myendpoint/messages</code> endpoint in the sample above) or other microservices that are part of the message processing logic in system under test.</p>
<p>In additional to all of that the BDD tests describe the given context, the events to occur and the expected outcome all in one single feature file. This declarative testing approach
is a perfect match to the concept of operators and custom resources on Kubernetes that is being used in so many Cloud-Native services these days.</p>
<h2 id="how-does-it-work">How does it work?</h2>
<p>YAKS provides a Kubernetes operator and a set of CRDs (custom resources) that we need to install in the cluster. The best way to install YAKS is to use the <em>yaks</em> CLI tools
that you can download on the <a href="https://github.com/citrusframework/yaks/releases">github release pages</a>.</p>
<p>Simply run:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ yaks install
</code></pre></div></div>
<p>This command prepares your Kubernetes cluster for running tests with YAKS. It will take care of installing the YAKS CRDs, setting up privileges and create the
operator in the current namespace.</p>
<p><strong>Important:</strong> in some cluster configurations, you need to be a cluster admin to install a CRD (it’s a operation that should be done only once for the entire cluster).
The <em>yaks</em> binary will help you troubleshoot. If you want to work on a development cluster like Minishift or Minikube, you can easily follow the dev cluster setup guide.</p>
<p>Once the cluster is prepared and the operator installed in the current namespace we can start to write a first BDD feature:</p>
<p>File: <em>http-to-kafka.feature</em></p>
<figure class="highlight"><pre><code class="language-gherkin" data-lang="gherkin"><span class="kd">Feature</span><span class="p">:</span> Http To Kafka
<span class="kn">Background</span><span class="p">:</span>
<span class="err">Given URL</span><span class="p">:</span> <span class="err">https</span><span class="p">:</span><span class="err">//camelk-sample.namespace.domain.example/api/resources</span>
<span class="kn">Scenario</span><span class="p">:</span> Get a result from API
<span class="nf">When </span>send GET /
<span class="nf">Then </span>receive HTTP 200 OK</code></pre></figure>
<p>You can run the test with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ yaks test http-to-kafka.feature
</code></pre></div></div>
<p>When you run this you will be provided with the test output from the Pod that is running your test in the current namespace on Kubernetes.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ yaks test http-to-kafka.feature
[...]
[INFO]
[INFO] CITRUS TEST RESULTS
[INFO]
[INFO] http-to-kafka.feature:6 .................. SUCCESS
[INFO]
[INFO] TOTAL: 1
[INFO] FAILED: 0 (0.0%)
[INFO] SUCCESS: 1 (100.0%)
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Generated test report: target/citrus-reports/citrus-test-results.html
1 Scenarios (1 passed)
2 Steps (2 passed)
0m1.711s
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.274 s - in org.citrusframework.yaks.YaksTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.453 s
[INFO] Finished at: 2020-03-06T15:30:53Z
[INFO] ------------------------------------------------------------------------
Test result: Passed
</code></pre></div></div>
<p>Also you can revisit the test Pod outcome with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get tests
</code></pre></div></div>
<p>This is an example output you should get:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NAME PHASE
http-to-kafka Passed
</code></pre></div></div>
<p>The test we just have run calls the sample Camel K integration over Http. It invokes the Http request on the Camel K service and verifies the <em>Http 200 OK</em> response code. The
test uses some of the ready-to-use steps for handling Http communication provided by YAKS. But before we have a closer look at the predefined YAKS steps that you can use in a feature file
we have a closer look at what happens behind the scenes when running this test.</p>
<p><a href="/assets/images/yaks-architecture.png"><img src="/assets/images/introducing-yaks/yaks-architecture.png" alt="YAKS architecture" /></a></p>
<p>The <code class="language-plaintext highlighter-rouge">yaks</code> tool will sync your test code with a Kubernetes custom resource of Kind <em>Test</em>. The resource is named <code class="language-plaintext highlighter-rouge">http-to-kafka</code> (after the file name) in the current namespace. So every time
you run the test the custom resource is updated and executed.</p>
<p>The YAKS operator is the component that makes all this possible by configuring all Kubernetes resources needed for running your tests.</p>
<p>Now lets have a look at the predefined YAKS step implementations that you can use out of the box.</p>
<h1 id="yaks-test-steps">YAKS test steps</h1>
<p>The test framework provides several ready-to-use <a href="https://cucumber.io/">Cucumber</a> steps that you can just use in your feature files. These steps should enable you to verify serverless applications by
exchanging data over various messaging transports. Have a look at the predefined steps we have so far:</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>yaks-standard</td>
<td>Basic steps (e.g. for logging messages to the console)</td>
</tr>
<tr>
<td>yaks-http</td>
<td>Call Http REST endpoints and verify the response content</td>
</tr>
<tr>
<td>yaks-swagger</td>
<td>Import Swagger OpenAPI specifications and use defined operations and test data to call Http REST endpoints</td>
</tr>
<tr>
<td>yaks-jdbc</td>
<td>Connect to a database for updating data or verifying query result sets</td>
</tr>
<tr>
<td>yaks-camel</td>
<td>Create, start and stop <a href="https://camel.apache.org/">Apache Camel</a> routes as part of the test. This opens access to all <strong>200+</strong> Camel components for testing!</td>
</tr>
<tr>
<td>yaks-camel-k</td>
<td>Create and verify Camel K integrations in a test</td>
</tr>
</tbody>
</table>
<p>The list of ready-to-use steps is constantly growing and you can also write your own steps and use them in a YAKS test! Have a look at the <a href="https://github.com/citrusframework/yaks/tree/master/examples">examples</a>
to see all those steps in action.</p>
<p>The steps provided in YAKS are implemented using the integration test framework <a href="https://citrusframework.org/">Citrus</a>.</p>
<p>Citrus itself also has some ready-to-use steps (e.g. <a href="https://citrusframework.org/citrus/reference/2.8.0/html/index.html#selenium-steps">Selenium steps</a> and
<a href="https://citrusframework.org/citrus/reference/2.8.0/html/index.html#docker-steps">Docker steps</a>). You can also use these steps as part of YAKS testing.</p>
<h1 id="demo">Demo</h1>
<p>The following demo video shows an example of what you can do with YAKS. It has the YAKS operator already installed and invokes a system under test via Http
verifying the response content. It continues to more complex scenarios where the test uses a Swagger OpenAPI specification for generating requests and test data.</p>
<p>Take a look:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/fR-UgzvZkuA" frameborder="0" allowfullscreen=""></iframe>
<h1 id="whats-next">What’s next</h1>
<p>This is just the start of a new testing platform for BDD testing in Cloud-Native environments! We are happy to receive feedback, ideas and of course contributions. Please add your thoughts
on the github repository by opening <a href="https://github.com/citrusframework/yaks/issues">new issues</a> or just share your appreciation for instance by <a href="https://github.com/citrusframework/yaks">giving a star</a> on the repository.</p>Christoph DeppischYAKS is Cloud-Native BDD testing or simply “Yet Another Kubernetes Service”.YAY! Citrus 3.0 is coming2020-03-19T00:00:00+01:002020-03-19T00:00:00+01:00http://christophd.github.io/blog/citrus-3.0-is-coming<p>YAY, we have started to work on Citrus 3.0! Yesterday we have released a first milestone <a href="https://github.com/citrusframework/citrus/releases/tag/v3.0.0-M1">3.0.0-M1</a>.
This is a big step in the Citrus project!</p>
<p>In the following I would like to share our strategy, ideas and major changes that are part of Citrus 3.0!</p>
<h1 id="objectives">Objectives</h1>
<p>Citrus 3.0 is a major release and we want to take that as an opportunity to follow up with some improvements and refactoring
that we are eager to do for quite some time. That being said we try to comply with your need to migrate from older versions so
we have a <a href="https://github.com/citrusframework/citrus/wiki/Migration-from-Citrus-2.x">migration guide</a> ready for people coming
from Citrus 2.x. In addition to that we take extra care to keep the breaking changes to a required level.</p>
<p>Here are the main objectives we have in Citrus 3.0</p>
<ul>
<li><a href="#modularize-citrus">Modularize Citrus</a></li>
<li><a href="#java-dsl-refactoring">Java DSL refactoring</a> (Designer vs. Runner)</li>
<li><a href="#make-spring-optional">Make Spring optional</a></li>
<li><a href="#update-dependencies">Update dependencies</a> to new major versions (Cucumber, Apache Camel, Spring Framework, …)</li>
</ul>
<h2 id="modularize-citrus">Modularize Citrus</h2>
<p>The <code class="language-plaintext highlighter-rouge">citrus-core</code> module is the heart of the framework and contains all capabilities that Citrus has to offer.
So if you include <code class="language-plaintext highlighter-rouge">citrus-core</code> as a dependency in your project you will load a lot of artifacts as transitive dependencies (e.g. from Maven central).
Loading that huge amount of libraries is not a good thing especially when you do not need all features provided by Citrus (e.g. Groovy script support, Xhtml, XML validation and so on).</p>
<p>With <code class="language-plaintext highlighter-rouge">citrus-core</code> it is all or nothing. So we want to modularize the core module into several smaller pieces. The user can then choose which of the
Citrus modules to include into the project or even overwrite or substitute pieces with own implementations as one likes.</p>
<h3 id="what-happened-to-citrus--model-modules">What happened to citrus-*-model modules?</h3>
<p>Each module in former Citrus versions has had a little brother that generated model classes from XSD schema files.
The XSD schemas are used for custom Spring bean definition parsing and were located in the <code class="language-plaintext highlighter-rouge">citrus-*-model</code> modules (e.g. citrus-config.xsd).
The initial idea behind that separate model module was to separate model classes from implementations in order to use that model in a user interface called <code class="language-plaintext highlighter-rouge">citrus-admin</code>.
With Citrus 3.x we included the XSD schemas into the implementation modules so we do not have to maintain all the <code class="language-plaintext highlighter-rouge">citrus-*-model</code> modules.</p>
<h3 id="module-categories-and-structure">Module categories and structure</h3>
<p>In Citrus 3.0 we end up with following module categories:</p>
<ul>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/core">Core modules</a></p>
<p>API and base implementations of core Citrus features. There will be a separate <code class="language-plaintext highlighter-rouge">citrus-base</code> and <code class="language-plaintext highlighter-rouge">citrus-core-spring</code> module
where latter encapsulates the Spring Framework support in Citrus.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-api</td>
<td>Interfaces, enums, constants</td>
</tr>
<tr>
<td>citrus-base</td>
<td>Default implementation of <code class="language-plaintext highlighter-rouge">citrus-api</code></td>
</tr>
<tr>
<td>citrus-core-spring</td>
<td>Adds Spring Framework support to <code class="language-plaintext highlighter-rouge">citrus-base</code> (Bean definition parsers, Application context configuration, Autowiring in factory beans</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/runtime">Runtime modules</a></p>
<p>Test execution modules such as JUnit, TestNG and Cucumber representing different ways to run Citrus tests.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-cucumber</td>
<td>Run Citrus tests as Cucumber BDD feature files</td>
</tr>
<tr>
<td>citrus-testng</td>
<td>Run tests via TestNG unit test framework</td>
</tr>
<tr>
<td>citrus-junit</td>
<td>Run tests via JUnit4 unit test framework</td>
</tr>
<tr>
<td>citrus-junit5</td>
<td>Run tests via JUnit5 unit test framework</td>
</tr>
<tr>
<td>citrus-main</td>
<td>Run tests via Java main CLI</td>
</tr>
<tr>
<td>citrus-arquillian</td>
<td>Run Citrus tests with Arquillian framework</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/endpoints">Endpoint modules</a></p>
<p>Endpoints connect Citrus to a message transport like JMS, Http REST, FTP, Mail and many more. Each endpoint may provide
client and/or server side implementation to exchange messages via a messaging transport.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-camel</td>
<td>Interact with Apache Camel context, routes and control bus</td>
</tr>
<tr>
<td>citrus-ftp</td>
<td>Connect to and simulate FTP/SFTP servers</td>
</tr>
<tr>
<td>citrus-http</td>
<td>Http REST support</td>
</tr>
<tr>
<td>citrus-jdbc</td>
<td>Simulate JDBC drivers, connections and transactions</td>
</tr>
<tr>
<td>citrus-jms</td>
<td>Publish/consume messages on a JMS message broker</td>
</tr>
<tr>
<td>citrus-kafka</td>
<td>Exchange data via Kafka messaging</td>
</tr>
<tr>
<td>citrus-jmx</td>
<td>Call MBean operations and simulate MBeans</td>
</tr>
<tr>
<td>citrus-mail</td>
<td>Client and server side SMTP mail support</td>
</tr>
<tr>
<td>citrus-rmi</td>
<td>Call RMI via JNDI registry lookup and simulate RMI services</td>
</tr>
<tr>
<td>citrus-ssh</td>
<td>Connect to servers via SSH and simulate SSH servers</td>
</tr>
<tr>
<td>citrus-vertx</td>
<td>Exchange messages on the Vert.x event bus</td>
</tr>
<tr>
<td>citrus-websocket</td>
<td>Websocket support</td>
</tr>
<tr>
<td>citrus-ws</td>
<td>SOAP WebServices support including SOAP envelope handling, WSDL, WS-Security, …</td>
</tr>
<tr>
<td>citrus-zookeeper</td>
<td>Connect with Zookeeper servers</td>
</tr>
<tr>
<td>citrus-spring-integration</td>
<td>Exchange messages on Spring Integration message channels</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/validation">Validation modules</a></p>
<p>When Citrus receives message content the test case is eager to verify the message content. Validation modules implement
message validators and mechanisms to validate different data formats such as Json, XML, Plaintext, Binary and so on.
Some validation modules also add support for verification tools such as Groovy script validation, Hamcrest and AssertJ.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-validation-xml</td>
<td>XML, Xpath and Xhtml message validation</td>
</tr>
<tr>
<td>citrus-validation-json</td>
<td>Json and JsonPath message validation</td>
</tr>
<tr>
<td>citrus-validation-text</td>
<td>Plain text message validation</td>
</tr>
<tr>
<td>citrus-validation-binary</td>
<td>Validate binary message content using input streams or <code class="language-plaintext highlighter-rouge">base64</code> encoding</td>
</tr>
<tr>
<td>citrus-validation-groovy</td>
<td>Adds Groovy script validation for XML, Json, SQL result set</td>
</tr>
<tr>
<td>citrus-validation-hamcrest</td>
<td>Hamcrest matcher support like <code class="language-plaintext highlighter-rouge">assertThat(oneOf(is(foo), is(foobar)))</code></td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/connectors">Connector modules</a></p>
<p>Connectors are similar to endpoints connecting Citrus to a foreign technology or framework though rather than implementing a message
transport like endpoint usually do. Connectors typically provide a client side only implementation that enable Citrus to interact with
a service or framework (e.g. Docker deamon, Selenium web driver).</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-docker</td>
<td>Connect with Docker deamon to manage images and containers</td>
</tr>
<tr>
<td>citrus-selenium</td>
<td>Connect with web driver to run web-based UI tests</td>
</tr>
<tr>
<td>citrus-kubernetes</td>
<td>Connect to Kubernetes cluster managing PODs services and other resources</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/tools">Tools</a></p>
<p>Tooling is important and the modules in this category provide little helpers and plugins for different use cases where the usage of
Citrus needs to be simplified (e.g. Maven plugins, test generators, etc.)</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-restdocs</td>
<td>Auto generate request/response documentation for Http REST and SOAP communication</td>
</tr>
<tr>
<td>citrus-maven-plugin</td>
<td>Maven plugins to create tests</td>
</tr>
<tr>
<td>citrus-test-generator</td>
<td>Create and auto generate test cases (e.g. from Swagger OpenAPI specifications)</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/catalog">Catalog modules</a></p>
<p>A catalog in Citrus combines several other modules into a set of modules that usually get used together. The <code class="language-plaintext highlighter-rouge">citrus-core</code> module
for instance combines all available validation modules, runtimes and the Citrus Spring support into a single artifact.
So the user just needs to add <code class="language-plaintext highlighter-rouge">citrus-core</code> to the project and can use everything Citrus has to offer.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-bom</td>
<td>Bill of material holding all modules for imports</td>
</tr>
<tr>
<td>citrus-core</td>
<td>Default Citrus capabilities (validation, runtime, Spring support) combined into one single module (exactly the same what you have had with previous versions)</td>
</tr>
<tr>
<td>citrus-endpoint-catalog</td>
<td>Combine all endpoints to a single source for endpoint builders</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/vintage">Vintage modules</a></p>
<p>We are about to take a major step in Citrus and this implies some backward incompatibilities that <em>“vintage”</em> modules try to
solve for users that still need to stick with an older version of Citrus for some reason. With these <em>“vintage”</em> modules you can
still run older test cases prior to 3.x with the new 3.x code base.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-java-dsl</td>
<td>Old Java DSL implementation (designer vs. runner) to be used for 2.x Java tests</td>
</tr>
</tbody>
</table>
</li>
<li>
<p><a href="https://github.com/citrusframework/citrus/tree/master/utils">Utility modules</a></p>
<p>Module in the utility category provide tooling for internal usage only. For instance this is a shared test library that
is used in unit testing by several other modules. The modules are only used when building the Citrus modules. Utility modules
usually are not included in a release so they won’t be pushed to Maven central.</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>citrus-test-support</td>
<td>Internal helper library added as test scoped dependency for unit testing in other modules. Holds shared unit testing helpers.</td>
</tr>
</tbody>
</table>
</li>
</ul>
<h3 id="how-to-use-the-new-module-structure">How to use the new module structure</h3>
<p>Users that do not want to change much in their project regarding the dependency setup just continue to add <code class="language-plaintext highlighter-rouge">citrus-core</code> dependency.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-core<span class="nt"></artifactId></span>
<span class="nt"><version></span>${project.version}<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>This will get you the same capabilities as in 2.x with all validation modules, runtime and Spring support enabled. The <code class="language-plaintext highlighter-rouge">citrus-core</code> is a
catalog module combining several other modules that get automatically added to your project.</p>
<p>The downside of this approach is that you get a lot of features and transitive dependencies that you might not need in your project.
Fortunately you can exclude some features from <code class="language-plaintext highlighter-rouge">citrus-core</code> with the new module structure in 3.x.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-core<span class="nt"></artifactId></span>
<span class="nt"><version></span>${project.version}<span class="nt"></version></span>
<span class="nt"><exclusions></span>
<span class="nt"><exclusion></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-validation-groovy<span class="nt"></artifactId></span>
<span class="nt"></exclusion></span>
<span class="nt"><exclusion></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-testng<span class="nt"></artifactId></span>
<span class="nt"></exclusion></span>
<span class="nt"></exclusions></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>The example above excludes the Groovy validation capabilities and the TestNG runtime from the project. The features will not be added
to your project and less artifacts get downloaded.</p>
<p>Of course there is a lot more to exclude and you might end up having a more complicated configuration for all those exclusions.
For people trying to operate with just what they need in their project the pull approach might be the way to go. Here you add just <code class="language-plaintext highlighter-rouge">citrus-base</code> as dependency.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-base<span class="nt"></artifactId></span>
<span class="nt"><version></span>${project.version}<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>If you want to use Spring Framework support you may also add:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-core-spring<span class="nt"></artifactId></span>
<span class="nt"><version></span>${project.version}<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>As you write and execute tests in your project you might then run into errors because you are using a Citrus feature that has not yet been added to your project. Something like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FAILURE: Caused by: NoSuchValidationMatcherException: Can not find validation matcher "assertThat" in library citrusValidationMatcherLibrary ()
at com/consol/citrus/jms/integration/JmsTopicDurableSubscriberIT(iterate:26-48)
</code></pre></div></div>
<p>With that error given you need to add the Hamcrest validation matcher feature to the project:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-validation-hamcrest<span class="nt"></artifactId></span>
<span class="nt"><version></span>${project.version}<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>Cool thing about it is that in case you prefer to use AssertJ matcher implementation instead you can add this dependency
(we still need to add AssertJ support in Citrus so we would love a contribution doing exactly that!).</p>
<h2 id="java-dsl-refactoring">Java DSL refactoring</h2>
<p>Citrus provides a Java DSL to write integration tests with a fluent API. The API makes use of fluent builder pattern to specify test
actions. All test action builder were combined in a single <code class="language-plaintext highlighter-rouge">citrus-java-dsl</code> module. For better maintainability the test action builders
have been moved into the individual modules where the test action implementation is located. In fact the builder are not inner classes
of the respective test action.</p>
<p>In former releases users had to choose from two different approaches to write tests with this fluent API: Test Designer and Test Runner.
We have a separate chapters in user guide describing the two different approaches for <a href="https://citrusframework.org/citrus/reference/2.8.0/html/index.html#java-dsl-test-designer">designer</a>
and <a href="https://citrusframework.org/citrus/reference/2.8.0/html/index.html#java-dsl-test-runner">runner</a>.</p>
<p>As many things in life both approaches have some advantages and of course downsides to offer. Citrus 3.x will only have one way to
write Java test cases using one single fluent API. We try to combine both approaches designer and runner into a single approach that
hopefully combines only the advantages and minimizes downsides.</p>
<h3 id="vintage-test-designer-approach">“Vintage” Test Designer approach</h3>
<p>The <em>“old”</em> designer approach has a nice fluent API that people tend to understand intuitively. Yet the designer separates test
design time and runtime which leads to unexpected behavior when someone needs to mix custom code with Java DSL execution.
Also debugging is not really an option as the whole test gets built first and then executed at the very end. Setting a break point
at design time of the test does not really help.</p>
<h3 id="vintage-test-runner-approach">“Vintage” Test Runner approach</h3>
<p>The <em>“old”</em> test runner avoids the separation of design time and runtime and executes each test action immediately. This enables
better debugging options and behaves like you would expect when writing custom Java code in your test. On the downside the test runner
fluent API makes use of lots of lambda expressions which is not a problem in general but still many people struggle to understand the
concept and the boundaries of lambdas in Java.</p>
<h3 id="the-new-testcaserunner-solution">The new TestCaseRunner solution</h3>
<p>In Citrus 3.x we end put in a simplified Java DSL that uses the look and feel of the former designer API but executes each step
immediately to keep debugging options and the capability to add custom code between steps.</p>
<p>The separation between designer and runner has been removed completely. So there is only one single source of truth the
<code class="language-plaintext highlighter-rouge">TestCaseRunner</code> which also implements <code class="language-plaintext highlighter-rouge">TestActionRunner</code>. This simplifies the implementation in other modules (Cucumber, TestNG, JUnit)
a lot.</p>
<p>This is how a new Java DSL test looks like:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HelloServiceIT</span> <span class="kd">extends</span> <span class="nc">TestNGCitrusSupport</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">HttpClient</span> <span class="n">httpClient</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">HttpServer</span> <span class="n">httpServer</span><span class="o">;</span>
<span class="nd">@Test</span>
<span class="nd">@CitrusTest</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">test</span><span class="o">()</span> <span class="o">{</span>
<span class="n">given</span><span class="o">(</span><span class="n">http</span><span class="o">().</span><span class="na">client</span><span class="o">(</span><span class="n">httpClient</span><span class="o">)</span>
<span class="o">.</span><span class="na">send</span><span class="o">()</span>
<span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"/hello"</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">));</span>
<span class="n">when</span><span class="o">(</span><span class="n">http</span><span class="o">().</span><span class="na">server</span><span class="o">(</span><span class="n">httpServer</span><span class="o">)</span>
<span class="o">.</span><span class="na">receive</span><span class="o">()</span>
<span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"/hello"</span><span class="o">));</span>
<span class="n">then</span><span class="o">(</span><span class="n">http</span><span class="o">().</span><span class="na">server</span><span class="o">(</span><span class="n">httpServer</span><span class="o">)</span>
<span class="o">.</span><span class="na">send</span><span class="o">()</span>
<span class="o">.</span><span class="na">response</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello from Citrus!"</span><span class="o">));</span>
<span class="n">then</span><span class="o">(</span><span class="n">http</span><span class="o">().</span><span class="na">client</span><span class="o">(</span><span class="n">httpClient</span><span class="o">)</span>
<span class="o">.</span><span class="na">receive</span><span class="o">()</span>
<span class="o">.</span><span class="na">response</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello from Citrus!"</span><span class="o">));</span>
<span class="n">then</span><span class="o">(</span><span class="n">doFinally</span><span class="o">().</span><span class="na">actions</span><span class="o">(</span>
<span class="n">stop</span><span class="o">(</span><span class="n">httpServer</span><span class="o">)</span>
<span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The test extends <code class="language-plaintext highlighter-rouge">TestNGCitrusSupport</code>. This gives you the annotation support for <code class="language-plaintext highlighter-rouge">@CitrusTest</code> so the test is added to the Citrus test
reporting. The base class also gives you the test action execution methods <code class="language-plaintext highlighter-rouge">given()</code>, <code class="language-plaintext highlighter-rouge">when()</code>, <code class="language-plaintext highlighter-rouge">then()</code> and <code class="language-plaintext highlighter-rouge">and()</code>.
This relates to the BDD Gherkin language and is widely known to a lot of people out there. If you do not want to use this BDD approach
in your test you can also use the basic <code class="language-plaintext highlighter-rouge">run()</code> method instead.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">run</span><span class="o">(</span><span class="n">http</span><span class="o">().</span><span class="na">client</span><span class="o">(</span><span class="n">httpClient</span><span class="o">)</span>
<span class="o">.</span><span class="na">send</span><span class="o">()</span>
<span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"/hello"</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">));</span></code></pre></figure>
<p><code class="language-plaintext highlighter-rouge">TestNGCitrusSupport</code> now is the single base class for all tests that use TestNG as base framework to run tests. This includes
XML and Java DSL tests. Former Citrus versions used several different base classes which confused users.</p>
<p>Same approach applies to <code class="language-plaintext highlighter-rouge">JUnit4CitrusSupport</code> for using JUnit 4. The JUnit 5 support provides a <code class="language-plaintext highlighter-rouge">CitrusSupport</code> extension.</p>
<h3 id="testactionbuilder">TestActionBuilder</h3>
<p>The Java DSL in Citrus consists of many actions that a user can choose from. In former Citrus versions all action methods were
combined into a single class named <code class="language-plaintext highlighter-rouge">TestDesigner</code> or <code class="language-plaintext highlighter-rouge">TestRunner</code>. All action methods followed the fluent Java builder pattern style.
The implementation of these builders have been moved from <code class="language-plaintext highlighter-rouge">citrus-java-dsl</code> module to its individual modules.</p>
<p>Each TestAction implementation now provides also a fluent Java builder that can be used in the Java DSL. Also the action builder
provides a static entry method for users to enter a builder pattern style configuration using that builder.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">EchoAction</span> <span class="kd">extends</span> <span class="nc">AbstractTestAction</span> <span class="o">{</span>
<span class="cm">/** Log message */</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">message</span><span class="o">;</span>
<span class="cm">/** Logger */</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Logger</span> <span class="n">log</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">EchoAction</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="cm">/**
* Default constructor using the builder.
* @param builder
*/</span>
<span class="kd">private</span> <span class="nf">EchoAction</span><span class="o">(</span><span class="nc">EchoAction</span><span class="o">.</span><span class="na">Builder</span> <span class="n">builder</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="s">"echo"</span><span class="o">,</span> <span class="n">builder</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">message</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">message</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doExecute</span><span class="o">(</span><span class="nc">TestContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">message</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Citrus test "</span> <span class="o">+</span> <span class="k">new</span> <span class="nc">Date</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()));</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">replaceDynamicContentInString</span><span class="o">(</span><span class="n">message</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="cm">/**
* Gets the message.
* @return the message
*/</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getMessage</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">message</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* Action builder.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Builder</span> <span class="kd">extends</span> <span class="nc">AbstractTestActionBuilder</span><span class="o"><</span><span class="nc">EchoAction</span><span class="o">,</span> <span class="nc">Builder</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">message</span><span class="o">;</span>
<span class="cm">/**
* Fluent API action building entry method used in Java DSL.
* @param message
* @return
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">Builder</span> <span class="nf">echo</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Builder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Builder</span><span class="o">();</span>
<span class="n">builder</span><span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
<span class="k">return</span> <span class="n">builder</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Builder</span> <span class="nf">message</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">message</span> <span class="o">=</span> <span class="n">message</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">EchoAction</span> <span class="nf">build</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">EchoAction</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>With this refactoring all test actions are now immutable and can only instantiate via the builder. You can use the action builders
in the your test like this:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">com.consol.citrus.annotations.CitrusTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.testng.TestNGCitrusSupport</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.testng.annotations.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">com</span><span class="o">.</span><span class="na">consol</span><span class="o">.</span><span class="na">citrus</span><span class="o">.</span><span class="na">actions</span><span class="o">.</span><span class="na">EchoAction</span><span class="o">.</span><span class="na">Builder</span><span class="o">.</span><span class="na">echo</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EchoActionJavaIT</span> <span class="kd">extends</span> <span class="nc">TestNGCitrusSupport</span> <span class="o">{</span>
<span class="nd">@Test</span>
<span class="nd">@CitrusTest</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">shouldEcho</span><span class="o">()</span> <span class="o">{</span>
<span class="n">run</span><span class="o">(</span><span class="n">echo</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">));</span>
<span class="n">run</span><span class="o">(</span><span class="n">echo</span><span class="o">(</span><span class="s">"Today is citrus:currentDate()"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>You can use static imports to add the action builders to your test.</p>
<h3 id="spring-factory-beans">Spring factory beans</h3>
<p>The new test action fluent Java builder design requires us to introduce Spring factory beans that add <code class="language-plaintext highlighter-rouge">Autowiring</code> and connect the
action builder to a bean definition parser. The factory beans live directly in the respective bean definition parser and take care on
injecting dependencies to the action builder.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">EchoActionParser</span> <span class="kd">implements</span> <span class="nc">BeanDefinitionParser</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">BeanDefinition</span> <span class="nf">parse</span><span class="o">(</span><span class="nc">Element</span> <span class="n">element</span><span class="o">,</span> <span class="nc">ParserContext</span> <span class="n">parserContext</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">BeanDefinitionBuilder</span> <span class="n">beanDefinition</span> <span class="o">=</span> <span class="nc">BeanDefinitionBuilder</span><span class="o">.</span><span class="na">rootBeanDefinition</span><span class="o">(</span><span class="nc">EchoActionFactoryBean</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">DescriptionElementParser</span><span class="o">.</span><span class="na">doParse</span><span class="o">(</span><span class="n">element</span><span class="o">,</span> <span class="n">beanDefinition</span><span class="o">);</span>
<span class="nc">Element</span> <span class="n">messageElement</span> <span class="o">=</span> <span class="nc">DomUtils</span><span class="o">.</span><span class="na">getChildElementByTagName</span><span class="o">(</span><span class="n">element</span><span class="o">,</span> <span class="s">"message"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">messageElement</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">beanDefinition</span><span class="o">.</span><span class="na">addPropertyValue</span><span class="o">(</span><span class="s">"message"</span><span class="o">,</span> <span class="nc">DomUtils</span><span class="o">.</span><span class="na">getTextValue</span><span class="o">(</span><span class="n">messageElement</span><span class="o">));</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">beanDefinition</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">();</span>
<span class="o">}</span>
<span class="cm">/**
* Test action factory bean.
*/</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">EchoActionFactoryBean</span> <span class="kd">extends</span> <span class="nc">AbstractTestActionFactoryBean</span><span class="o"><</span><span class="nc">EchoAction</span><span class="o">,</span> <span class="nc">EchoAction</span><span class="o">.</span><span class="na">Builder</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">EchoAction</span><span class="o">.</span><span class="na">Builder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EchoAction</span><span class="o">.</span><span class="na">Builder</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setMessage</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="n">builder</span><span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">EchoAction</span> <span class="nf">getObject</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">builder</span><span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Class</span><span class="o"><?></span> <span class="n">getObjectType</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">EchoAction</span><span class="o">.</span><span class="na">class</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/**
* Obtains the builder.
* @return the builder implementation.
*/</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">EchoAction</span><span class="o">.</span><span class="na">Builder</span> <span class="nf">getBuilder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">builder</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The factory beans can use <code class="language-plaintext highlighter-rouge">@Autowired</code> and bean lifecycle hooks such as <code class="language-plaintext highlighter-rouge">InitializingBean</code> or <code class="language-plaintext highlighter-rouge">ApplicationContextAware</code>.
These Spring related features were moved to the factory beans. This way we can decouple <code class="language-plaintext highlighter-rouge">citrus-api</code> and <code class="language-plaintext highlighter-rouge">citrus-base</code> from Spring
making it an optional library to use in Citrus.</p>
<h2 id="make-spring-optional">Make Spring optional</h2>
<p>The Spring framework provides an awesome set of projects, libraries and tools and is a wide spread and well appreciated framework for Java.
The dependency injection and IoC concepts introduced with Spring are still awesome.</p>
<p>Some people prefer to choose other approaches though to work with dependency injection. Others do struggle with mastering Citrus and
Spring as new frameworks at the same time. Both frameworks Spring and Citrus are very powerful and newbies sometimes feel overwhelmed
with having to deal with so many new stuff at a time.</p>
<p>In former releases Citrus has been very tied to Spring and in some cases this has been a show stopper to work with Citrus for mentioned
reasons.</p>
<p>In Citrus 3.x we make Spring optional in <code class="language-plaintext highlighter-rouge">core</code> modules so people can choose how to work with the framework. In particular this affects
the way Citrus components are started and linked to each other.</p>
<h3 id="direct-endpoint">Direct endpoint</h3>
<p>By default Citrus server endpoints (e.g. Http server, Mail server, …) are using some in memory message channel for incoming requests.
This internal message channel used Spring integration as implementation. In Citrus 3.x we changed this to a custom in memory message
queue implementation called DirectEndpoint. This was done to decouple Citrus core from the Spring integration library.</p>
<p>The DirectEndpoint lives in the <code class="language-plaintext highlighter-rouge">citrus-base</code> module and replaces the Spring integration message channel implementation as a default for
all server endpoints.</p>
<p>The Spring integration message channel endpoint is not lost though. Users can still use this implementation as the endpoint was
extracted from <code class="language-plaintext highlighter-rouge">citrus-core</code> to a separate endpoint module named <code class="language-plaintext highlighter-rouge">citrus-spring-integration</code>.</p>
<h3 id="citrus-with-spring-enabled">Citrus with Spring enabled</h3>
<p>When Spring is enabled for Citrus all components are loaded with a Spring application context. This enables autowiring and bean
definition parsing. Latter bean definition parsing for custom components is mandatory when using XML based configuration and XML
test cases in Citrus.</p>
<p>Users enable the Spring support in Citrus by adding:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-core-spring<span class="nt"></artifactId></span>
<span class="nt"><version></span>${project.version}<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>When using <code class="language-plaintext highlighter-rouge">citrus-core</code> dependency this Spring support is enabled by default in order to adjust with what has been configured in
previous Citrus versions.</p>
<h3 id="citrus-standalone">Citrus standalone</h3>
<p>In case you exclude the <code class="language-plaintext highlighter-rouge">citrus-core-spring</code> module for Citrus you will load the same components and features but only without
Spring framework support. Keep in mind only the XML based configuration and XML test cases continue to require Spring. All other features in
Citrus are ok to not work with a Spring bean application context.</p>
<p>In non-Spring mode custom components can be directly configured in the Citrus context then. Also Citrus uses a resource common path
lookup mechanism to identify common components that get loaded automatically. So you simply add components such as <code class="language-plaintext highlighter-rouge">citrus-validation-json</code> to your
project classpath and the Json validation capabilities are loaded automatically.</p>
<p>Feel free to choose which approach fits best for your needs. Citrus running with or without Spring.</p>
<h2 id="refactor-internals">Refactor internals</h2>
<p>Citrus is over 20 years old! The project has seen many refactorings and improvements over time. Still there is a lot to tidy
up and improve in terms of reducing technical dept and simplify code. We do our best to accomplish this to keep the code base
maintainable yet not to break too many things for existing projects out there.</p>
<p>When you look at the code base for 3.0 you will se a lot of changes related to things being moved and modularized into
several other modules. At the end you should be having a cleaner and simpler code base to work with, hopefully!</p>
<h2 id="update-dependencies">Update dependencies</h2>
<p>It has been quite some time since the last major Citrus release. So this is a point where we catch up with all the other libraries and
dependencies that evolved over time. In particular this is Apache Camel, Spring and Cucumber that all evolved with major versions in the past.</p>
<p>Citrus 3.0 is up to date with using the latest versions of these libraries and the following might be the most important updates:</p>
<ul>
<li>Spring framework 5.2</li>
<li>Apache Camel 3.1</li>
<li>TestNG 7.1</li>
<li>JUnit 5.4</li>
<li>Jetty</li>
<li>Arquillian 1.6</li>
<li>Zookeeper</li>
<li>Kafka</li>
<li>Selenium</li>
<li>Ssh/Ftp</li>
<li>Slf4j/Log4J2</li>
<li>Cucumber 5.4</li>
</ul>
<h2 id="whats-next">What’s next!?</h2>
<p>So this is the first milestone of the Citrus 3.x release train. And we are not done yet! We will continue to work on our goals
to finish the 3.0 release with working on modularization, making Spring optional and to simplify the Java DSL.</p>
<p>We would love to get feedback and early adopters so please give it a try and tell us what you think about it. Now is the time to raise your voice to improve the 3.0 content!</p>Christoph DeppischYAY, we have started to work on Citrus 3.0! Yesterday we have released a first milestone 3.0.0-M1. This is a big step in the Citrus project!Arquillian & Citrus in combination - Part 22015-08-19T00:00:00+02:002015-08-19T00:00:00+02:00http://christophd.github.io/blog/arquillian-extension-part-2<p>Some time has passed since <a href="http://christophd.github.io/arquillian-extension-part-1/" title="Part 1" target="_blank">part one</a> of this blog post series and we have made some improvements on the Citrus Arquillian extension.
So we can look forward to this post as we move on with a more complex test scenario where we include some Citrus mail server within our test. In part one we have already combined both frameworks <a href="http://arquillian.org/" title="Arquillian" target="_blank">Arquillian</a>
and <a href="http://www.citrusframework.org" title="Citrus framework" target="_blank">Citrus</a> with a basic example.</p>
<p>Let me recap first what we have so far. Citrus is part of the Arquillian test execution and we are able to call our employee REST JEE service which is deployed in an embedded Wildfly application server.
Arquillian takes care on the test deployment and provides resources such as the endpoint URL to call. In our first test we called the employee service adding new employees and getting the complete list of all employees via REST.</p>
<p>Now we want to extend the employee service so that each new employee gets a welcome email message. Therefore we extend the employee JEE EJB with a mail session bean that is able to send email messages.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Singleton</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EmployeeRepository</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Employees</span> <span class="n">employees</span><span class="o">;</span>
<span class="nd">@EJB</span>
<span class="kd">private</span> <span class="nc">MailSessionBean</span> <span class="n">mailSessionBean</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">EmployeeRepository</span><span class="o">()</span> <span class="o">{</span>
<span class="n">employees</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Employees</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addEmployee</span><span class="o">(</span><span class="nc">Employee</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">employees</span><span class="o">.</span><span class="na">getEmployees</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getEmail</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">e</span><span class="o">.</span><span class="na">getEmail</span><span class="o">().</span><span class="na">length</span><span class="o">()</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mailSessionBean</span><span class="o">.</span><span class="na">sendMail</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getEmail</span><span class="o">(),</span> <span class="s">"Welcome new employee"</span><span class="o">,</span>
<span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"We welcome you '%s' to our company - now get to work!"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getName</span><span class="o">()));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Employees</span> <span class="nf">getEmployees</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">employees</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The employee service calls a mail session bean implementation when the email field is set on the new employee. The mail message is sent to the employee mail address as recipient and is a basic text message with subject and text body part.
Lets have a closer look at the mail session bean implementation that uses Java mail API for creating sending out the mail mime message.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Singleton</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MailSessionBean</span> <span class="o">{</span>
<span class="cm">/** Logger */</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">Logger</span> <span class="n">log</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">MailSessionBean</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">port</span> <span class="o">=</span> <span class="mi">2222</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">host</span> <span class="o">=</span> <span class="s">"localhost"</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">from</span> <span class="o">=</span> <span class="s">"employee-registry@example.com"</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">username</span> <span class="o">=</span> <span class="s">"employee-registry@example.com"</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">password</span> <span class="o">=</span> <span class="s">"secretpw"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sendMail</span><span class="o">(</span><span class="nc">String</span> <span class="n">to</span><span class="o">,</span> <span class="nc">String</span> <span class="n">subject</span><span class="o">,</span> <span class="nc">String</span> <span class="n">body</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Properties</span> <span class="n">props</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Properties</span><span class="o">();</span>
<span class="n">props</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"mail.smtp.host"</span><span class="o">,</span> <span class="n">host</span><span class="o">);</span>
<span class="n">props</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"mail.smtp.port"</span><span class="o">,</span> <span class="n">port</span><span class="o">);</span>
<span class="n">props</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"mail.smtp.auth"</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="nc">Authenticator</span> <span class="n">authenticator</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Authenticator</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">PasswordAuthentication</span> <span class="n">pa</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PasswordAuthentication</span><span class="o">(</span><span class="n">username</span><span class="o">,</span> <span class="n">password</span><span class="o">);</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">PasswordAuthentication</span> <span class="nf">getPasswordAuthentication</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">pa</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="nc">Session</span> <span class="n">session</span> <span class="o">=</span> <span class="nc">Session</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">props</span><span class="o">,</span> <span class="n">authenticator</span><span class="o">);</span>
<span class="n">session</span><span class="o">.</span><span class="na">setDebug</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="nc">MimeMessage</span> <span class="n">message</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MimeMessage</span><span class="o">(</span><span class="n">session</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">message</span><span class="o">.</span><span class="na">setFrom</span><span class="o">(</span><span class="k">new</span> <span class="nc">InternetAddress</span><span class="o">(</span><span class="n">from</span><span class="o">));</span>
<span class="nc">InternetAddress</span><span class="o">[]</span> <span class="n">address</span> <span class="o">=</span> <span class="o">{</span><span class="k">new</span> <span class="nc">InternetAddress</span><span class="o">(</span><span class="n">to</span><span class="o">)};</span>
<span class="n">message</span><span class="o">.</span><span class="na">setRecipients</span><span class="o">(</span><span class="nc">Message</span><span class="o">.</span><span class="na">RecipientType</span><span class="o">.</span><span class="na">TO</span><span class="o">,</span> <span class="n">address</span><span class="o">);</span>
<span class="n">message</span><span class="o">.</span><span class="na">setSubject</span><span class="o">(</span><span class="n">subject</span><span class="o">);</span>
<span class="n">message</span><span class="o">.</span><span class="na">setSentDate</span><span class="o">(</span><span class="k">new</span> <span class="nc">Date</span><span class="o">());</span>
<span class="n">message</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="n">body</span><span class="o">);</span>
<span class="nc">Transport</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">MessagingException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Failed to send welcome mail!"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>Now in our test scenario we need a valid SMTP mail server on host <strong>localhost</strong> and port <strong>2222</strong>. Fortunately Citrus is able to provide such a mail server so we add the <strong>citrus-mail</strong> Maven module to our test project.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-mail<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.3<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>Now we are ready to add the mail server as a Citrus component. Citrus is working with Spring so we need to add the mail server to the Spring application context. We add a new property to the Arquillian descriptor so the Citrus extension will
load our new configuration:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><extension</span> <span class="na">qualifier=</span><span class="s">"citrus"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"citrusVersion"</span><span class="nt">></span>2.3<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"autoPackage"</span><span class="nt">></span>true<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"suiteName"</span><span class="nt">></span>citrus-arquillian-suite<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"configurationClass"</span><span class="nt">></span>com.consol.citrus.samples.javaee.config.CitrusConfig<span class="nt"></property></span>
<span class="nt"></extension></span></code></pre></figure>
<p>The configuration class is a Spring Java configuration class in our project that adds the new mail server as Spring bean to the application context.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CitrusConfig</span> <span class="kd">extends</span> <span class="nc">CitrusBaseConfig</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">MailServer</span> <span class="n">mailServer</span><span class="o">;</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">MailServer</span> <span class="nf">mailServer</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mailServer</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mailServer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MailServer</span><span class="o">();</span>
<span class="n">mailServer</span><span class="o">.</span><span class="na">setPort</span><span class="o">(</span><span class="mi">2222</span><span class="o">);</span>
<span class="n">mailServer</span><span class="o">.</span><span class="na">setAutoStart</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">mailServer</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The configuration class must extend the Citrus base configuration class. This basic class adds all needed Spring beans for Citrus such as message validators, functions, validation matchers and so on. Important is our new mail server that uses the port
<strong>2222</strong> and automatically starts with the Citrus application context. This is everything we need to do in order to add the mail server component to the Citrus runtime. We can add other Citrus endpoint components such as JMS endpoints, SOAP WebService servers
and Camel endpoints here the same way. For now we are finished with the configuration and we can reference the mail server in our test case.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@RunWith</span><span class="o">(</span><span class="nc">Arquillian</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@RunAsClient</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EmployeeMailTest</span> <span class="o">{</span>
<span class="nd">@CitrusFramework</span>
<span class="kd">private</span> <span class="nc">Citrus</span> <span class="n">citrusFramework</span><span class="o">;</span>
<span class="nd">@ArquillianResource</span>
<span class="kd">private</span> <span class="no">URL</span> <span class="n">baseUri</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">serviceUri</span><span class="o">;</span>
<span class="nd">@CitrusEndpoint</span>
<span class="kd">private</span> <span class="nc">MailServer</span> <span class="n">mailServer</span><span class="o">;</span>
<span class="nd">@Deployment</span><span class="o">(</span><span class="n">testable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">WebArchive</span> <span class="nf">createDeployment</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">ShrinkWrap</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="nc">WebArchive</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">addClasses</span><span class="o">(</span>
<span class="nc">RegistryApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MailSessionBean</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">EmployeeResource</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
<span class="nc">Employees</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">Employee</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">EmployeeRepository</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="n">serviceUri</span> <span class="o">=</span> <span class="k">new</span> <span class="no">URL</span><span class="o">(</span><span class="n">baseUri</span><span class="o">,</span> <span class="s">"registry/employee"</span><span class="o">).</span><span class="na">toExternalForm</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="nd">@CitrusTest</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testPostWithWelcomeEmail</span><span class="o">(</span><span class="nd">@CitrusResource</span> <span class="nc">TestDesigner</span> <span class="n">citrus</span><span class="o">)</span> <span class="o">{</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">(</span><span class="s">"name=Rajesh&age=20&email=rajesh@example.com"</span><span class="o">)</span>
<span class="o">.</span><span class="na">method</span><span class="o">(</span><span class="nc">HttpMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_FORM_URLENCODED</span><span class="o">));</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">mailServer</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"<mail-message xmlns=\"http://www.citrusframework.org/schema/mail/message\">"</span> <span class="o">+</span>
<span class="s">"<from>employee-registry@example.com</from>"</span> <span class="o">+</span>
<span class="s">"<to>rajesh@example.com</to>"</span> <span class="o">+</span>
<span class="s">"<cc></cc>"</span> <span class="o">+</span>
<span class="s">"<bcc></bcc>"</span> <span class="o">+</span>
<span class="s">"<subject>Welcome new employee</subject>"</span> <span class="o">+</span>
<span class="s">"<body>"</span> <span class="o">+</span>
<span class="s">"<contentType>text/plain; charset=us-ascii</contentType>"</span> <span class="o">+</span>
<span class="s">"<content>We welcome you 'Rajesh' to our company - now get to work!</content>"</span> <span class="o">+</span>
<span class="s">"</body>"</span> <span class="o">+</span>
<span class="s">"</mail-message>"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="nc">CitrusMailMessageHeaders</span><span class="o">.</span><span class="na">MAIL_SUBJECT</span><span class="o">,</span> <span class="s">"Welcome new employee"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="nc">CitrusMailMessageHeaders</span><span class="o">.</span><span class="na">MAIL_FROM</span><span class="o">,</span> <span class="s">"employee-registry@example.com"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="nc">CitrusMailMessageHeaders</span><span class="o">.</span><span class="na">MAIL_TO</span><span class="o">,</span> <span class="s">"rajesh@example.com"</span><span class="o">);</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">NO_CONTENT</span><span class="o">));</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">()</span>
<span class="o">.</span><span class="na">method</span><span class="o">(</span><span class="nc">HttpMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="o">.</span><span class="na">accept</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_XML</span><span class="o">));</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">(</span><span class="s">"<employees>"</span> <span class="o">+</span>
<span class="s">"<employee>"</span> <span class="o">+</span>
<span class="s">"<age>20</age>"</span> <span class="o">+</span>
<span class="s">"<name>Rajesh</name>"</span> <span class="o">+</span>
<span class="s">"<email>rajesh@example.com</email>"</span> <span class="o">+</span>
<span class="s">"</employee>"</span> <span class="o">+</span>
<span class="s">"</employees>"</span><span class="o">)</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">));</span>
<span class="n">citrusFramework</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="n">citrus</span><span class="o">.</span><span class="na">getTestCase</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The Arquillian test case is basically the same as before in our first part example. The test now uses a <strong>@CitrusEndpoint</strong> annotated mail server component. The Arquillian Citrus extension will automatically inject the
mail server Spring bean that we have added to the Citrus configuration before. In case multiple components of the same type are available in the configuration you can use the <strong>@CitrusEndpoint</strong> annotation with a Spring bean name
like <strong>@CitrusEndpoint(name=”myMailServer”)</strong>. We receive the mail message right after the test has called the service interface with the new employee <strong>Rajesh</strong> and a valid email field <strong>rajesh@example.com</strong>. The mail server Citrus component
is ready to be used in a <strong>receive</strong> test action. Citrus waits for the mail message to arrive and performs message validation with an expected mail message. Citrus automatically converts the mail mime message to a XML message representation.
We can expect the mail message content much better using the XML syntax as we can use the powerful XML validation with XPath, ignore and validation matcher support.</p>
<p>In addition to that Citrus adds some special header values for explicit validation in the <strong>receive</strong> action. That completes the mail server test case. Citrus automatically start a SMTP server that receives the mail message. The mail is not sent to
a real recipient we just want to validate the mail message content in our test. The mail server did automatically accept the authentication in default mode. We could also switch to advanced mode where we can also validate the authentication steps. For now we keep
it simple and are happy to receive the mail message. Please note that we can mix the receive actions of different interfaces in Citrus very easy. Citrus acts both as client and server on our REST and mail interfaces in the test.</p>
<p>This should close the book for this post. You can review the example code on <a href="https://github.com/christophd/citrus-samples/tree/master/javaee" title="Sample code" target="_blank">christophd@github</a>. In a next post I will show you how we can execute
the test as part of the test deployment on the application server. Then we have direct access to container managed resources such as JMS connection factories and queues. Once again, stay tuned!</p>Christoph DeppischSome time has passed since part one of this blog post series and we have made some improvements on the Citrus Arquillian extension. So we can look forward to this post as we move on with a more complex test scenario where we include some Citrus mail server within our test. In part one we have already combined both frameworks Arquillian and Citrus with a basic example.Arquillian & Citrus in combination - Part 12015-06-29T00:00:00+02:002015-06-29T00:00:00+02:00http://christophd.github.io/blog/arquillian-extension-part-1<p><a href="http://www.citrusframework.org" title="Citrus framework" target="_blank">Citrus</a> and <a href="http://arquillian.org/" title="Arquillian" target="_blank">Arquillian</a>
both refer to themselves as integration test frameworks. Following from that you might think these frameworks somehow ship the same package but this is not the case. In fact the frameworks work
brilliant together when it comes to automate the integration testing of JEE applications.</p>
<p>Arquillian has a focus on simplifying the archive deployment with the ability to run integration test cases inside the boundaries of an application container.
The tests are literally part of a server deployment. This approach brings great advantages such as accessing deployment and container resources within the test case.
The application is deployed to a real application server (e.g. Tomcat, Wildfly) which is a good idea as we then also test our application server configuration and we load
the application with all container managed resources such as database connections, JNDI resources and so on. Secondly the test case itself is able to directly use the
container managed resources with injection in the Arquillian test case which is a great thing to do! It becomes very easy to invoke your services in a close to production
nature then.</p>
<p>Citrus primarily has the objective to simplify the usage of different messaging transports in a test case. Citrus offers ready to use components for sending and receiving
messages as a client or server and helps to design a message flow across multiple service calls.</p>
<p>In combination the two frameworks manage complex integration test scenarios that perform fully automated. And this is what we want to have at the end of a day - fully automated integration tests.
The magic is possible since Arquillian offers a great extension mechanism and Citrus provides an Arquillian extension module. Once the extension is enabled in your Arquillian project
you can use Citrus features and components within a your Arquillian test case.</p>
<p>Lets have a look at this with a small example:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><extension</span> <span class="na">qualifier=</span><span class="s">"citrus"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"citrusVersion"</span><span class="nt">></span>2.3<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"autoPackage"</span><span class="nt">></span>true<span class="nt"></property></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"suiteName"</span><span class="nt">></span>citrus-arquillian-suite<span class="nt"></property></span>
<span class="nt"></extension></span></code></pre></figure>
<p>The extension configuration is placed in the basic Arquillian descriptor called <strong>arquillian.xml</strong>. We use the <em>citrus</em> qualifier so the settings are automatically loaded with the Citrus extension.</p>
<p>For now the possible extension settings are:</p>
<ul>
<li><strong>citrusVersion:</strong> The explicit version of Citrus that should be used. Be sure to have the same library version available in your project (e.g. as Maven dependency). This property is optional. By default the extension just uses the latest stable version.</li>
<li><strong>autoPackage:</strong> When true (default setting) the extension will automatically add Citrus libraries and all transitive dependencies to the test deployment. This automatically enables you to use the Citrus API inside the Arquillian test even when the test is executed inside the application container.</li>
<li><strong>suiteName:</strong> This optional setting defines the name of the test suite that is used for the Citrus test run. When using before/after suite functionality in Citrus this setting might be of interest.</li>
<li><strong>configurationClass:</strong> Full qualified Java class name of customized Citrus Spring bean configuration to use when loading the Citrus Spring application context. As a user you can define a custom Citrus configuration with this optional setting.</li>
</ul>
<p>So we also need to add the Citrus Maven dependency to our project:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-arquillian<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.2<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>The dependency automatically adds the required Citrus core dependencies so you are ready to work with Citrus framework components. In case you want to use special Citrus components like JMS, FTP, Mail or something like that you need to
add these modules each separately to your Maven POM, too. For now we want to use Http clients and the Citrus Java DSL so lets add the dependencies for that.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-http<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.2<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-java-dsl<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.2<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>Now we are ready to integrate Citrus in a first Arquillian test case. As an example service we are using a simple REST service. The service is called <strong>employee registry</strong> and provides a REST interface for adding, listing, updating and deleting employees in a registry.
The employee REST resource uses the usual Http methods GET, UPDATE, DELETE, PUT for managing the employee entries. Lets call the service via Http and add a new employee to the registry.:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@RunWith</span><span class="o">(</span><span class="nc">Arquillian</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@RunAsClient</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EmployeeResourceTest</span> <span class="o">{</span>
<span class="nd">@CitrusFramework</span>
<span class="kd">private</span> <span class="nc">Citrus</span> <span class="n">citrusFramework</span><span class="o">;</span>
<span class="nd">@ArquillianResource</span>
<span class="kd">private</span> <span class="no">URL</span> <span class="n">baseUri</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">serviceUri</span><span class="o">;</span>
<span class="nd">@Deployment</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">WebArchive</span> <span class="nf">createDeployment</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">ShrinkWrap</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="nc">WebArchive</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="o">.</span><span class="na">addClasses</span><span class="o">(</span><span class="nc">RegistryApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">EmployeeResource</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
<span class="nc">Employees</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">Employee</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">EmployeeRepository</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Before</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">MalformedURLException</span> <span class="o">{</span>
<span class="n">serviceUri</span> <span class="o">=</span> <span class="k">new</span> <span class="no">URL</span><span class="o">(</span><span class="n">baseUri</span><span class="o">,</span> <span class="s">"registry/employee"</span><span class="o">).</span><span class="na">toExternalForm</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Test</span>
<span class="nd">@CitrusTest</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">testCreateEmployeeAndGet</span><span class="o">(</span><span class="nd">@CitrusResource</span> <span class="nc">TestDesigner</span> <span class="n">citrus</span><span class="o">)</span> <span class="o">{</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">(</span><span class="s">"name=Penny&age=20"</span><span class="o">)</span>
<span class="o">.</span><span class="na">method</span><span class="o">(</span><span class="nc">HttpMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">)</span>
<span class="o">.</span><span class="na">contentType</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_FORM_URLENCODED</span><span class="o">));</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">()</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">NO_CONTENT</span><span class="o">));</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">()</span>
<span class="o">.</span><span class="na">method</span><span class="o">(</span><span class="nc">HttpMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">)</span>
<span class="o">.</span><span class="na">accept</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_XML</span><span class="o">));</span>
<span class="n">citrus</span><span class="o">.</span><span class="na">receive</span><span class="o">(</span><span class="n">serviceUri</span><span class="o">)</span>
<span class="o">.</span><span class="na">message</span><span class="o">(</span><span class="k">new</span> <span class="nc">HttpMessage</span><span class="o">(</span><span class="s">"<employees>"</span> <span class="o">+</span>
<span class="s">"<employee>"</span> <span class="o">+</span>
<span class="s">"<age>20</age>"</span> <span class="o">+</span>
<span class="s">"<name>Penny</name>"</span> <span class="o">+</span>
<span class="s">"</employee>"</span> <span class="o">+</span>
<span class="s">"</employees>"</span><span class="o">)</span>
<span class="o">.</span><span class="na">statusCode</span><span class="o">(</span><span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">));</span>
<span class="n">citrusFramework</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="n">citrus</span><span class="o">.</span><span class="na">getTestCase</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>As you can see this is a normal Arquillian JUnit test case. We are running the test in client mode with the <strong>@RunAsClient</strong> annotation. As the
Citrus extension is active in background we are able to inject the framework instance with the <strong>@CitrusFramework</strong> annotation. The Citrus
framework instance is automatically loaded and configured with all necessary settings in background when the Arquillian test starts. As usual
we can build the deployment archive with our server resources that we need to test and we have access to <strong>@ArquillianResource</strong> annotated
resources such as the REST service endpoint URI of our application.</p>
<p>The test method itself is provided with a <strong>@CitrusTest</strong> annotation marking the test as aware of Citrus. In combination with that we use the <strong>@CitrusResource</strong>
annotated method parameter that represents the Citrus test designer. This is a Java DSL representation of what Citrus has to offer when it comes to sending and receiving messages over various message transports.
The Citrus Arquillian extension will automatically inject this method parameter so we can use it inside the test method block in order to define the Citrus test logic.</p>
<p>Basically we invoke the deployed REST service with Citrus using the Java DSL <em>send</em> and <em>receive</em> methods. Each receive operation in Citrus also triggers the message validation mechanism. This includes
a syntax check in case of SOAP and XML messages with a WSDL or XSD given and a semantic check on received message body and header values. The tester is able to give an expected message template that is used
as comparison template. Citrus is able to deeply walk through XML or JSON message payloads comparing elements, attributes, namespaces and values. In case the received message response does not match the given message template
the Arquillian test case will fail with respective validation errors. For now lets keep it simple so we just expect some Http response status codes in the receive operations. The last receive operation expects a very simple XML
message payload with the newly added employee data.</p>
<p>With the ability to send and receive messages via Http REST we simply invoke the deployed service endpoint and validate its outcome. With a sequence of send and receive operations in Citrus we can build complex message flows with
multiple service endpoints involved.</p>
<p>That’s it we have successfully combined both frameworks. Arquillian helps us to deploy and run our application inside an application container and we have easy access to managed resources. Citrus is able to invoke the services via Http with
powerful validation of the received response messages. This is just a very simple sample for now as I just wanted to show the basic setup. Based on this knowledge we can continue with a more complex test scenario. We will start
to use Arquillian in container testing mode and we will start to use more complex Citrus server components in one of my next posts. Stay tuned!</p>Christoph DeppischCitrus and Arquillian both refer to themselves as integration test frameworks. Following from that you might think these frameworks somehow ship the same package but this is not the case. In fact the frameworks work brilliant together when it comes to automate the integration testing of JEE applications.Apache Camel Integration Testing - Part 32015-06-03T00:00:00+02:002015-06-03T00:00:00+02:00http://christophd.github.io/blog/camel-testing-part-3<p>In this post I will continue with the Apache Camel integration testing scenario that we have worked on in
<a href="http://christophd.github.io/camel-testing-part-1/" title="Part 1" target="_blank">part one</a> and
<a href="http://christophd.github.io/camel-testing-part-2/" title="Part 2" target="_blank">part two</a> of this series.
This time we focus on exception handling in Camel routes. First of all let’s add exception handling to our Camel route.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><camelContext</span> <span class="na">id=</span><span class="s">"camelContext"</span> <span class="na">xmlns=</span><span class="s">"http://camel.apache.org/schema/spring"</span><span class="nt">></span>
<span class="nt"><route</span> <span class="na">id=</span><span class="s">"helloRoute"</span><span class="nt">></span>
<span class="nt"><from</span> <span class="na">uri=</span><span class="s">"direct:hello"</span><span class="nt">/></span>
<span class="nt"><to</span> <span class="na">uri=</span><span class="s">"seda:sayHello"</span> <span class="na">pattern=</span><span class="s">"InOut"</span><span class="nt">/></span>
<span class="nt"><onException></span>
<span class="nt"><exception></span>com.consol.citrus.exceptions.CitrusRuntimeException<span class="nt"></exception></span>
<span class="nt"><to</span> <span class="na">uri=</span><span class="s">"seda:errors"</span><span class="nt">/></span>
<span class="nt"></onException></span>
<span class="nt"></route></span>
<span class="nt"></camelContext></span></code></pre></figure>
<p>Camel supports exception handling on specific exception types. The <em>onException</em> route logic is executed when the defined exception type was raised
during message processing. In the sample route we call a separate Seda endpoint <em>seda:errors</em> for further exception handling. The challenge for our
test scenario is obvious. We need to force this error handling and we need to make sure that the Seda endpoint <em>seda:errors</em> has been called accordingly.</p>
<p>Let’s add the error handling endpoint to the Citrus Spring bean configuration.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><citrus-camel:sync-endpoint</span> <span class="na">id=</span><span class="s">"sedaErrorHandlingEndpoint"</span>
<span class="na">camel-context=</span><span class="s">"camelContext"</span>
<span class="na">endpoint-uri=</span><span class="s">"seda:errors"</span><span class="nt">/></span></code></pre></figure>
<p>The static endpoint definition is not mandatory as Citrus is also able to work with dynamic endpoints. In our case the dynamic endpoint for consuming messages on
the <em>seda:errors</em> endpoint would simply be <em>camel:sync:seda:errors</em>. The decision which kind of endpoint someone should use depends
on how much the endpoint could be reused throughout multiple test scenarios. Of course static endpoints are highly reusable in different test cases. On the downside
we have to manage the additional configuration burden. In this post I will use the static endpoint that is injected to the test case via Spring’s autowiring mechanism.
Let’s write the integration test.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.consol.citrus.hello</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.TestNGCitrusTestBuilder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.annotations.CitrusTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.endpoint.Endpoint</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.testng.annotations.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.beans.factory.annotation.Autowired</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.beans.factory.annotation.Qualifier</span><span class="o">;</span>
<span class="cm">/**
* @author Christoph Deppisch
*/</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SayHelloTest</span> <span class="kd">extends</span> <span class="nc">TestNGCitrusTestBuilder</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"directHelloEndpoint"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Endpoint</span> <span class="n">directHelloEndpoint</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"sedaHelloEndpoint"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Endpoint</span> <span class="n">sedaHelloEndpoint</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"sedaErrorHandlingEndpoint"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Endpoint</span> <span class="n">sedaErrorHandlingEndpoint</span><span class="o">;</span>
<span class="nd">@CitrusTest</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"SayHello_Error_Test"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sayHello_Error_Test</span><span class="o">()</span> <span class="o">{</span>
<span class="n">send</span><span class="o">(</span><span class="n">directHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="n">sedaHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">);</span>
<span class="n">sleep</span><span class="o">(</span><span class="mi">500L</span><span class="o">);</span>
<span class="n">send</span><span class="o">(</span><span class="n">sedaHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Something went wrong!"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"citrus_camel_exchange_exception"</span><span class="o">,</span>
<span class="s">"com.consol.citrus.exceptions.CitrusRuntimeException"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"citrus_camel_exchange_exception_message"</span><span class="o">,</span>
<span class="s">"Something went wrong!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="n">sedaErrorHandlingEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Something went wrong!"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"CamelExceptionCaught"</span><span class="o">,</span>
<span class="s">"com.consol.citrus.exceptions.CitrusRuntimeException: Something went wrong!"</span><span class="o">);</span>
<span class="n">send</span><span class="o">(</span><span class="n">sedaErrorHandlingEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello after error!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="n">directHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello after error!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The magic happens when Citrus sends back a synchronous response on the <em>seda:sayHello</em> endpoint which is done right after the sleep action in our sample test.
Instead of responding with a usual plain text message we add special header values <strong>citrus_camel_exchange_exception</strong> and <strong>citrus_camel_exchange_exception_message</strong>.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">send</span><span class="o">(</span><span class="n">sedaHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Something went wrong!"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"citrus_camel_exchange_exception"</span><span class="o">,</span>
<span class="s">"com.consol.citrus.exceptions.CitrusRuntimeException"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"citrus_camel_exchange_exception_message"</span><span class="o">,</span>
<span class="s">"Something went wrong!"</span><span class="o">);</span></code></pre></figure>
<p>These special Citrus headers instruct the Citrus Camel endpoint to raise an exception. We are able to specify the exception type as well as the exception message. As a result
the Citrus endpoint raises the exception on the Camel exchange which should force the exception handling in our Camel route.</p>
<p>The route <em>onException</em> block in our example should send the error to the <em>seda:errors</em> endpoint. So let’s consume this message in a next test step.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">receive</span><span class="o">(</span><span class="n">sedaErrorHandlingEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Something went wrong!"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="s">"CamelExceptionCaught"</span><span class="o">,</span>
<span class="s">"com.consol.citrus.exceptions.CitrusRuntimeException: Something went wrong!"</span><span class="o">);</span></code></pre></figure>
<p>With this receive action on the error endpoint we intend to validate that the exception handling was done as expected. We are able to check the error message payload and in addition
to that we have access to the Camel internal message headers that indicate the exception handling. Both message payload and message headers are compared to expected values in Citrus.</p>
<p>As a next test step we should provide a proper response message that is used as fallback response. The response is sent back as synchronous response on the <em>seda:errors</em> endpoint saying <em>Hello after error!</em>.
The Camel <em>onException</em> block and in detail the default Camel error handler will use this message as final result of the route logic. So finally in our test we can receive the fallback response message as result
of our initial direct <em>direct:hello</em> request.</p>
<p>This completes this simple test scenario. We raised an exception and forced the Camel route to perform proper exception handling. With the Citrus endpoints in duty we received the error message and provided a fallback
response message which is used as final result of the Camel route.</p>
<p>Error handling in message based enterprise integration scenarios is complex. We need to deal with delivery timeouts, retry strategies and proper transaction handling. This post only showed the top of the iceberg but I hope
you got the idea of how to set up automated integration tests with Apache Camel routes. The <a href="http://www.citrusframework.org" title="Citrus framework" target="_blank">Citrus framework</a> is focused on providing real message endpoints no matter of what kind or nature (Http, JMS, REST, SOAP, Ftp, XML, JSON,
Seda and so on). What we get is automated integration testing with real messages being exchanged on different transports and endpoints.</p>Christoph DeppischIn this post I will continue with the Apache Camel integration testing scenario that we have worked on in part one and part two of this series. This time we focus on exception handling in Camel routes. First of all let’s add exception handling to our Camel route.Apache Camel Integration Testing - Part 22015-05-27T00:00:00+02:002015-05-27T00:00:00+02:00http://christophd.github.io/blog/camel-testing-part-2<p>In <a href="http://christophd.github.io/camel-testing-part-1/" title="Part 1" target="_blank">part one</a> of this blog series we have used Citrus in combination with Apache Camel for setting up
a complete integration test scenario. Remember we have interacted with our Camel route via JMS as client and via SOAP Http WebService as a server.</p>
<p>Now in the second part we want to interact with a Camel route using direct and Seda in memory message transports. First of all we need a Camel route to test.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><camelContext</span> <span class="na">id=</span><span class="s">"camelContext"</span> <span class="na">xmlns=</span><span class="s">"http://camel.apache.org/schema/spring"</span><span class="nt">></span>
<span class="nt"><route</span> <span class="na">id=</span><span class="s">"helloRoute"</span><span class="nt">></span>
<span class="nt"><from</span> <span class="na">uri=</span><span class="s">"direct:hello"</span><span class="nt">/></span>
<span class="nt"><to</span> <span class="na">uri=</span><span class="s">"seda:sayHello"</span> <span class="na">pattern=</span><span class="s">"InOut"</span><span class="nt">/></span>
<span class="nt"></route></span>
<span class="nt"></camelContext></span></code></pre></figure>
<p>The Camel route is obviously very simple. The route reads messages from a direct inbound endpoint and forwards all messages to a Seda in memory channel. The Seda communication is synchronous
so the route waits for a synchronous response to arrive. Now when we would like to test this simple route we would have to provide the inbound messages to trigger the route and we would have to
provide proper synchronous response messages on the Seda endpoint.</p>
<p>Lets set up a Citrus test case for this test scenario. We need a direct Camel route message endpoint that is able to call the Camel route. The Camel endpoints are available with a separate Citrus module.
We need to add this library to our project as test scoped dependency if not already done so.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.consol.citrus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>citrus-camel<span class="nt"></artifactId></span>
<span class="nt"><version></span>${citrus.version}<span class="nt"></version></span>
<span class="nt"><scope></span>test<span class="nt"></scope></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>Now we can use the Citrus Camel endpoint components in the Spring bean application context that is part of the Citrus project.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:citrus-camel=</span><span class="s">"http://www.citrusframework.org/schema/camel/config"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.citrusframework.org/schema/camel/config http://www.citrusframework.org/schema/camel/config/citrus-camel-config.xsd"</span><span class="nt">></span>
<span class="nt"><camelContext</span> <span class="na">id=</span><span class="s">"camelContext"</span> <span class="na">xmlns=</span><span class="s">"http://camel.apache.org/schema/spring"</span><span class="nt">></span>
<span class="nt"><route</span> <span class="na">id=</span><span class="s">"helloRoute"</span><span class="nt">></span>
<span class="nt"><from</span> <span class="na">uri=</span><span class="s">"direct:hello"</span><span class="nt">/></span>
<span class="nt"><to</span> <span class="na">uri=</span><span class="s">"seda:sayHello"</span> <span class="na">pattern=</span><span class="s">"InOut"</span><span class="nt">/></span>
<span class="nt"></route></span>
<span class="nt"></camelContext></span>
<span class="nt"><citrus-camel:sync-endpoint</span> <span class="na">id=</span><span class="s">"directHelloEndpoint"</span>
<span class="na">camel-context=</span><span class="s">"camelContext"</span>
<span class="na">endpoint-uri=</span><span class="s">"direct:hello"</span><span class="nt">/></span>
<span class="nt"><citrus-camel:sync-endpoint</span> <span class="na">id=</span><span class="s">"sedaHelloEndpoint"</span>
<span class="na">camel-context=</span><span class="s">"camelContext"</span>
<span class="na">endpoint-uri=</span><span class="s">"seda:sayHello"</span><span class="nt">/></span>
<span class="nt"></beans></span></code></pre></figure>
<p>As you can see we have added two Citrus endpoint components both coming from the Citrus Camel module. The first component is interacting with the direct endpoint <em>direct:hello</em> and the second component is interacting with the
Seda <em>seda:sayHello</em> endpoint. Both Citrus components use synchronous message communication so we are able to send and receive messages synchronously. Let’s move on with writing a test case.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.consol.citrus.hello</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.TestNGCitrusTestBuilder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.annotations.CitrusTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.endpoint.Endpoint</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.testng.annotations.Test</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.beans.factory.annotation.Autowired</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.springframework.beans.factory.annotation.Qualifier</span><span class="o">;</span>
<span class="cm">/**
* @author Christoph Deppisch
*/</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SayHelloTest</span> <span class="kd">extends</span> <span class="nc">TestNGCitrusTestBuilder</span> <span class="o">{</span>
<span class="nd">@Autowired</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"directHelloEndpoint"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Endpoint</span> <span class="n">directHelloEndpoint</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="nd">@Qualifier</span><span class="o">(</span><span class="s">"sedaHelloEndpoint"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Endpoint</span> <span class="n">sedaHelloEndpoint</span><span class="o">;</span>
<span class="nd">@CitrusTest</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"SayHello_Ok_Test"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sayHello_Ok_Test</span><span class="o">()</span> <span class="o">{</span>
<span class="n">send</span><span class="o">(</span><span class="n">directHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="n">sedaHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">);</span>
<span class="n">sleep</span><span class="o">(</span><span class="mi">500L</span><span class="o">);</span>
<span class="n">send</span><span class="o">(</span><span class="n">sedaHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Camel!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="n">directHelloEndpoint</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Camel!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>The test is using Spring’s autowiring mechanism in order to inject the Citrus message endpoint components. We have four message interactions in our test. First of all we send a plain text message to the direct Camel route endpoint.
The Camel route is triggered and according to the route logic the message is forwarded to the Seda endpoint. The second test action is a message receive action on this same Seda endpoint. So if the Camel route logic is working as
expected we should be able to receive a message here. The receive test action also performs a validation on the message content received. As a tester we specify the expected message payload. Citrus as test framework compares this
expectation to the message content actually arriving. When everything is matching as expected we continue with the test.</p>
<p>As the Seda endpoint is synchronous we can send back a response to the calling client. In our test this is done with a respective send message action that references the same Seda endpoint. Before we send back a plain text response message
we add a sleep test action in order to simulate some hard work on the backend. As a next step the Camel route receives our simulated response message and immediately responds to the calling direct endpoint client to complete the route.
This is our last step in the test case where we receive the very same response message on the direct endpoint as final message response.</p>
<p>Please do not get confused with this test setup. This scenario is purely constructed for demonstrating how Citrus interacts with Camel routes in terms of synchronous communication on both ends (consuming and producing). As the test performs
all actions four messages are exchanged in between Citrus and our Camel route to test. If everything is working as expected all messages are completed and the test case is successful.</p>
<p>Someone might feel uncomfortable in defining the Citrus endpoint components for each Camel route endpoint in the Spring application context just to reference them inside the test case. This is where dynamic endpoints come in handy. Citrus
is also able to create the endpoint at runtime. See how this looks like.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.consol.citrus.hello</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.TestNGCitrusTestBuilder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.annotations.CitrusTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.endpoint.Endpoint</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.testng.annotations.Test</span><span class="o">;</span>
<span class="cm">/**
* @author Christoph Deppisch
*/</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SayHelloTest</span> <span class="kd">extends</span> <span class="nc">TestNGCitrusTestBuilder</span> <span class="o">{</span>
<span class="nd">@CitrusTest</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"SayHello_Ok_Test"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sayHello_Ok_Test</span><span class="o">()</span> <span class="o">{</span>
<span class="n">send</span><span class="o">(</span><span class="s">"camel:sync:direct:hello"</span><span class="o">)</span>
<span class="o">.</span><span class="na">fork</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Camel!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="s">"camel:sync:seda:sayHello"</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Camel!"</span><span class="o">);</span>
<span class="n">sleep</span><span class="o">(</span><span class="mi">500L</span><span class="o">);</span>
<span class="n">send</span><span class="o">(</span><span class="s">"camel:sync:seda:sayHello"</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="nl">camel:sync:direct:</span><span class="n">hello</span><span class="o">)</span>
<span class="o">.</span><span class="na">messageType</span><span class="o">(</span><span class="nc">MessageType</span><span class="o">.</span><span class="na">PLAINTEXT</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"Hello Citrus!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>With the dynamic endpoints we do not have to use any of the predefined endpoint components in the Citrus Spring application context. The test just creates the endpoints automatically at runtime. The result is exactly the same as before.</p>
<p>We have seen that Citrus is able to both send and receive message from an to route message endpoints in Apache Camel. This is a good way of testing Camel routes with simulation of route interface partners. In the next part I will
add some error scenarios where the Seda endpoint component is forcing an exception that should be handled within our Camel route.</p>Christoph DeppischIn part one of this blog series we have used Citrus in combination with Apache Camel for setting up a complete integration test scenario. Remember we have interacted with our Camel route via JMS as client and via SOAP Http WebService as a server.Apache Camel Integration Testing - Part 12014-11-21T00:00:00+01:002014-11-21T00:00:00+01:00http://christophd.github.io/blog/camel-testing-part-1<p>Apache Camel is a great mediation and routing framework that integrates with almost every enterprise messaging transport. In the past I have experienced Camel projects struggling with integration testing where the actual message interfaces to boundary applications are not tested properly in an automated way.</p>
<p>So in a series of posts I would like to talk about integration testing strategies for Apache Camel projects using the Citrus integration test framework.</p>
<ul>
<li><a href="http://christophd.github.io/camel-testing-part-1/" title="Part 1" target="_blank">Part 1</a>: Setup the Citrus test project and interact with a sample Apache Camel project with JMS and SOAP WebService components</li>
<li><a href="http://christophd.github.io/camel-testing-part-2/" title="Part 2" target="_blank">Part 2</a>: Invoke Camel routes from Citrus test cases and validate outbound messages</li>
<li><a href="http://christophd.github.io/camel-testing-part-3/" title="Part 3" target="_blank">Part 3</a>: Test error situations with Camel exception handling</li>
</ul>
<p>So lets have a sample Camel project that we would like to test. We need a simple Camel route like this:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><camelContext</span> <span class="na">id=</span><span class="s">"camelContext"</span> <span class="na">xmlns=</span><span class="s">"http://camel.apache.org/schema/spring"</span><span class="nt">></span>
<span class="nt"><route</span> <span class="na">id=</span><span class="s">"newsRoute"</span><span class="nt">></span>
<span class="nt"><from</span> <span class="na">uri=</span><span class="s">"jms:queue:JMS.Queue.News"</span><span class="nt">/></span>
<span class="nt"><to</span> <span class="na">uri=</span><span class="s">"log:com.consol.citrus.camel?level=INFO"</span><span class="nt">/></span>
<span class="nt"><to</span> <span class="na">uri=</span><span class="s">"spring-ws:http://localhost:8080?soapAction=newsFeed"</span><span class="nt">/></span>
<span class="nt"></route></span>
<span class="nt"></camelContext></span></code></pre></figure>
<p>The route consumes messages from a JMS destination called <em>JMS.Queue.News</em>, logs the message content and forwards the message content to a Http SOAP WebService using the Spring WS library. So we have two different messaging interfaces (JMS and SOAP WebService) to boundary systems in our sample.</p>
<p>Camel does provide very good test strategies for mocking these message transports. In a unit test you can mock the boundary JMS and SOAP interfaces with special mock components. This is great, but sometimes error prone for the following reasons. The JMS protocol provides several settings that are essential for the whole application behavior. For instance the JMS consumer may operate with concurrent consumers, connection pooling and transactional behavior. These settings are done on the Camel JMS component and on the JMS connection factory. In a mocked unit test these settings are not included as the test mock just does not process the real JMS message transports. No doubt these settings make a significant difference in production and have to be tested. In addition to that the SOAP WebService interface uses a WSDL and other SOAP specific settings like the soap action header. We could also add WS-Security and WS-Addressing headers here. In a mocked unit test these quite important interface characteristics are not tested over the wire. The actual SOAP message is not created and you are not able to verify the complete message contents as they are sent over the wire.</p>
<p>So the crucial weapon to avoid bugs related to untested transport settings is integration testing where the actual JMS message broker and a real SOAP WebService endpoint are involved in the test. And this is where Citrus comes in. In a Citrus test case the actual JMS consumer and producer settings do apply as well as a fully qualified WebService endpoint that receives the messages via Http message protocol. We use a real JMS message broker and Http server as it is done in production.</p>
<p>Lets setup a Citrus project that is able to interact with the sample Camel application route.</p>
<p>Citrus as integration test framework works best with Maven. You can setup a Citrus Maven project in minutes with this <a href="http://www.citrusframework.org/tutorials-maven.html" title="Maven Quickstart" target="_blank">quick start tutorial</a>. Once you have done this we have a Maven project with some sample Citrus test cases already in it. No we need to add the JMS and SOAP WebService configuration to the Citrus project. First of all let us add ActiveMQ as JMS message broker.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.apache.activemq<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>activemq-broker<span class="nt"></artifactId></span>
<span class="nt"><version></span>${activemq.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.apache.activemq<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>activemq-spring<span class="nt"></artifactId></span>
<span class="nt"><version></span>${activemq.version}<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.apache.xbean<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>xbean-spring<span class="nt"></artifactId></span>
<span class="nt"><version></span>${xbean.version}<span class="nt"></version></span>
<span class="nt"></dependency></span></code></pre></figure>
<p>We need to add the ActiveMQ Maven dependencies to our Citrus project. This is done in the Maven POM <em>pom.xml</em> file. Once we have this we can add the message broker to the Citrus configuration. We add a new Spring application context file <em>citrus-activemq-context.xml</em> in <em>src/citrus/resources</em> folder.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><beans</span> <span class="na">xmlns=</span><span class="s">"http://www.springframework.org/schema/beans"</span>
<span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
<span class="na">xmlns:amq=</span><span class="s">"http://activemq.apache.org/schema/core"</span>
<span class="na">xsi:schemaLocation=</span><span class="s">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd"</span><span class="nt">></span>
<span class="c"><!-- Embedded ActiveMQ JMS broker --></span>
<span class="nt"><amq:broker</span> <span class="na">useJmx=</span><span class="s">"false"</span> <span class="na">persistent=</span><span class="s">"false"</span><span class="nt">></span>
<span class="nt"><amq:transportConnectors></span>
<span class="nt"><amq:transportConnector</span> <span class="na">uri=</span><span class="s">"tcp://localhost:61616"</span> <span class="nt">/></span>
<span class="nt"></amq:transportConnectors></span>
<span class="nt"></amq:broker></span>
<span class="nt"><bean</span> <span class="na">id=</span><span class="s">"connectionFactory"</span> <span class="na">class=</span><span class="s">"org.apache.activemq.ActiveMQConnectionFactory"</span><span class="nt">></span>
<span class="nt"><property</span> <span class="na">name=</span><span class="s">"brokerURL"</span> <span class="na">value=</span><span class="s">"tcp://localhost:61616"</span> <span class="nt">/></span>
<span class="nt"></bean></span>
<span class="nt"></beans></span></code></pre></figure>
<p>As next step we include this new Spring context file in the <em>citrus-context.xml</em> so both the ActiveMQ message broker and the JMS connection factory get loaded during startup. Just add a import statement to the <em>citrus-context.xml</em> file like this:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><import</span> <span class="na">resource=</span><span class="s">"classpath:citrus-activemq-context.xml"</span><span class="nt">/></span></code></pre></figure>
<p>Good! Now we are ready to connect with the JMS message transport. Let us add the Citrus JMS endpoints. You can do this also in the <em>citrus-context.xml</em> file:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><citrus-jms:endpoint</span> <span class="na">id=</span><span class="s">"newsJmsEndpoint"</span>
<span class="na">destination-name=</span><span class="s">"JMS.Queue.News"</span>
<span class="na">timeout=</span><span class="s">"5000"</span><span class="nt">/></span></code></pre></figure>
<p>That’s it! Now we can send and receive JMS messages on the JMS destination <em>JMS.Queue.News</em>. Ok so let’s write a new integration test! We create a new Java class in <em>src/citrus/java</em></p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="nn">com.consol.citrus.news</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.TestNGCitrusTestBuilder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.dsl.annotations.CitrusTest</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.consol.citrus.ws.message.SoapMessageHeaders</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.testng.annotations.Test</span><span class="o">;</span>
<span class="cm">/**
* @author Christoph Deppisch
*/</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">NewsFeedTest</span> <span class="kd">extends</span> <span class="nc">TestNGCitrusTestBuilder</span> <span class="o">{</span>
<span class="nd">@CitrusTest</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"NewsFeed_Ok_Test"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">newsFeed_Ok_Test</span><span class="o">()</span> <span class="o">{</span>
<span class="n">send</span><span class="o">(</span><span class="s">"newsJmsEndpoint"</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"<News>"</span> <span class="o">+</span>
<span class="s">"<Message>Citrus rocks!</Message>"</span> <span class="o">+</span>
<span class="s">"</News>"</span><span class="o">);</span>
<span class="n">receive</span><span class="o">(</span><span class="s">"newsSoapServer"</span><span class="o">)</span>
<span class="o">.</span><span class="na">payload</span><span class="o">(</span><span class="s">"<News>"</span> <span class="o">+</span>
<span class="s">"<Message>Citrus rocks!</Message>"</span> <span class="o">+</span>
<span class="s">"</News>"</span><span class="o">)</span>
<span class="o">.</span><span class="na">header</span><span class="o">(</span><span class="nc">SoapMessageHeaders</span><span class="o">.</span><span class="na">SOAP_ACTION</span><span class="o">,</span> <span class="s">"newsFeed"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>This represents a Citrus Java test case. Notice that this is nothing but a normal Java unit test. I use TestNG as unit test framework. Others might prefer JUnit which is also possible. I think TestNG is much more powerful but this is another story. Also notice that we referenced the <em>newsJmsEndpoint</em> Citrus component in the first send operation. We could have used Spring autowire injection of the JmsEndpoint here, but we want to keep it simple for now. What we have right now is an integration test which actually sends the JMS message with some news content to the JMS queue destination and on the other side we receive the real SOAP message as a web server. But wait! We have not yet added the SOAP server configuration yet! Let’s do this in the <em>citrus-context.xml</em> configuration file.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><citrus-ws:server</span> <span class="na">id=</span><span class="s">"newsSoapServer"</span>
<span class="na">port=</span><span class="s">"8080"</span>
<span class="na">auto-start=</span><span class="s">"true"</span>
<span class="na">timeout=</span><span class="s">"10000"</span><span class="nt">/></span></code></pre></figure>
<p>The server starts a fully qualified Http web server with the SOAP endpoint for receiving the news request. In the test case we can reference the server in a receive operation. That’s it! We are able to run the test. Of course we also need to start our Camel application. You can do this in another process with Maven or you deploy your Camel application to some application server. Citrus is interacting with the Camel route as a normal interface client just using the JMS endpoint. Once you run the integration test you will see how the messages are actualy sent over the wire and you will see Citrus receiving the actual SOAP request message:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml">Received SOAP request:
<span class="cp"><?xml version="1.0" encoding="UTF-8"?></span><span class="nt"><SOAP-ENV:Envelope</span> <span class="na">xmlns:SOAP-ENV=</span><span class="s">"http://schemas.xmlsoap.org/soap/envelope/"</span><span class="nt">></span>
<span class="nt"><SOAP-ENV:Header</span> <span class="na">jmsmessageid=</span><span class="s">"ID:localhost-50188-1416562129343-3:4:1:1:1"</span><span class="nt">/></span>
<span class="nt"><SOAP-ENV:Body></span>
<span class="nt"><News></span>
<span class="nt"><Message></span>Citrus rocks!<span class="nt"></Message></span>
<span class="nt"></News></span>
<span class="nt"></SOAP-ENV:Body></span>
<span class="nt"></SOAP-ENV:Envelope></span></code></pre></figure>
<p>Also Citrus will put this received message content to validation with comparing the message body and header to the expected content given in the test case. Let’s recap. What we have done is a complete integration test where the Camel route interfaces are called with real message transport interaction. The ActiveMQ JMS message broker is real the SOAP WebService server is real. The message transport configuration is tested properly with message conversion and expected message content validation. The Camel application is loaded and deployed with the complete configuration and settings.</p>
<p>Citrus invokes the Camel route by sending a proper JMS message to the route endpoint. The Camel route processes the message and sends out the SOAP request message. In case the message content is not as expected in the Citrus receive operation or in case the SOAP message is not arriving at all the integration test fails. In this integration test we can simulate both client and server side interaction with our Camel application.</p>
<p>As the Citrus test case is nothing but a normal TestNG/JUnit test we can integrate the test in our Maven build lifecycle and continuous integration process. This completes our first part of how to do extended integration testing with Apache Camel projects. In my next part I will concentrate on how to interact with Apache Camel routes using direct and Seda in memory message transports.</p>Christoph DeppischApache Camel is a great mediation and routing framework that integrates with almost every enterprise messaging transport. In the past I have experienced Camel projects struggling with integration testing where the actual message interfaces to boundary applications are not tested properly in an automated way.