{"id":8151,"date":"2014-12-18T05:55:36","date_gmt":"2014-12-18T13:55:36","guid":{"rendered":"http:\/\/localhost\/exoblog\/?p=8151"},"modified":"2023-06-05T16:49:49","modified_gmt":"2023-06-05T14:49:49","slug":"developing-juzu-portlets-step-7-finishing-the-job-properly-with-unit-test","status":"publish","type":"post","link":"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-7-finishing-the-job-properly-with-unit-test\/","title":{"rendered":"Developing Juzu portlets \u2013 Step 7: Finishing the job properly with unit test"},"content":{"rendered":"<p><a href=\"\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series.jpg\"><img decoding=\"async\" loading=\"lazy\" src=\"\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series.jpg\" alt=\"07-Juzu-tutorial-series\" width=\"650\" height=\"220\" class=\"aligncenter size-full wp-image-8166\" srcset=\"https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series.jpg 650w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series-300x102.jpg 300w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series-500x169.jpg 500w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series-360x122.jpg 360w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series-200x68.jpg 200w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series-100x34.jpg 100w, https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/12\/07-Juzu-tutorial-series-70x24.jpg 70w\" sizes=\"(max-width: 650px) 100vw, 650px\" \/><\/a><\/p>\n<p><em>Previous steps:<br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/learn-how-to-develop-great-juzu-portlets-for-exo-platform\/\">Learn how to develop great Juzu portlets for eXo Platform!<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-2-viewing-and-posting-secrets\/\">Step 2: viewing and posting secrets<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-3-building-a-sexy-secret-wall\/\">Step 3: Building a sexy secret wall<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-4-adding-likes-and-comments-for-secret\/\">Step 4: adding Likes and Comments for Secret<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-5-saving-secrets-in-the-jcr\/\">Step 5: Saving Secrets in the JCR<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-6-let-the-whole-world-create-new-secrets\/\">Step 6: Let the whole world create new secrets<\/a><\/em><\/p>\n<p>The JuZcret application is now finished, but our <strong>project is not perfect<\/strong>&#8230;<\/p>\n<p>All through the previous blog posts, we have neglected to tell you about <strong>Unit Test<\/strong>, and this certainly is not a good practice.<\/p>\n<p>The reason is that we wanted to keep you focused on a specific topic during each step. That\u2019s why it\u2019s only during this last step that we\u2019ll talk about <strong>Unit Test in Juzu<\/strong>.<\/p>\n<p>The good news is that Juzu allows you to leverage <strong>Selenium<\/strong> easily to simulate a real application while taking advantage of the speed of <strong>JUnit<\/strong>.<\/p>\n<p>So it\u2019s time to write Unit Test for our JuZcret portlet. The portlet will be deployed to an embedded portlet container.<strong> Selenium WebDriver<\/strong> will help to simulate almost all user interactions with the application, and then <strong>Arquillian<\/strong> will help to integrate with <strong>JUnit<\/strong>.<br \/>\n<!--more--><\/p>\n<h2>Dependencies<\/h2>\n<p>We will use:<\/p>\n<ul>\n<li><strong>JUnit 4<\/strong><\/li>\n<li><strong>Arquillian<\/strong>: A testing framework for managing containers and writing integration tests<\/li>\n<li><strong>ShrinkWrap<\/strong>: Arquillian\u2019s little brother, used for creating Java archives easily<\/li>\n<li><strong>Selenium WebDriver<\/strong>: A simple API for simulating browser behavior<\/li>\n<\/ul>\n<p>To make testing easy, Juzu provides Maven dependencies called <strong>depchains<\/strong> that contain all needed dependencies for testing an application with the following tools: Juzu, depchain, and Arquillian and Juzu, depchain, Arquillian, and Tomcat7, which should already be in your <span class=\"navCode\">pom.xml<\/span> file:<\/p>\n<pre class=\"lang:default decode:true \">&lt;dependency&gt;\n\t&lt;groupId&gt;junit&lt;\/groupId&gt;\n\t&lt;artifactId&gt;junit&lt;\/artifactId&gt;\n\t&lt;version&gt;4.10&lt;\/version&gt;\n\t&lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n\t&lt;groupId&gt;org.juzu&lt;\/groupId&gt;\n\t&lt;artifactId&gt;juzu-depchain-arquillian&lt;\/artifactId&gt;\n\t&lt;version&gt;1.0.0-cr1&lt;\/version&gt;\n\t&lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n\t&lt;groupId&gt;org.juzu&lt;\/groupId&gt;\n\t&lt;artifactId&gt;juzu-depchain-arquillian-tomcat7&lt;\/artifactId&gt;\n\t&lt;version&gt;1.0.0-cr1&lt;\/version&gt;\n\t&lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;<\/pre>\n<p>The Juzu core provides some <strong>abstract test classes<\/strong> that make it easier for our tests to interact with Arquillian. Let\u2019s add this dependency and plugin:<\/p>\n<pre class=\"lang:default decode:true \">[...]\n\n&lt;dependency&gt;\n\t&lt;groupId&gt;org.juzu&lt;\/groupId&gt;\n\t&lt;artifactId&gt;juzu-core&lt;\/artifactId&gt;\n\t&lt;version&gt;1.0.0-cr1&lt;\/version&gt;\n\t&lt;type&gt;test-jar&lt;\/type&gt;\n\t&lt;scope&gt;test&lt;\/scope&gt;\n&lt;\/dependency&gt;\n\n[...]\n\n&lt;!-- juzu-core test jar need this configuration --&gt;\n&lt;plugin&gt;\n\t&lt;groupId&gt;org.apache.maven.plugins&lt;\/groupId&gt;\n\t&lt;artifactId&gt;maven-surefire-plugin&lt;\/artifactId&gt;\n\t&lt;version&gt;2.16&lt;\/version&gt;\n\t&lt;configuration&gt;\n\t\t&lt;systemPropertyVariables&gt;\n\t\t\t&lt;targetDir&gt;${project.build.directory}&lt;\/targetDir&gt;\n\t\t\t&lt;juzu.test.compiler&gt;javac&lt;\/juzu.test.compiler&gt;\n\t\t\t&lt;juzu.test.resources.path&gt;${basedir}\/src\/test\/resources&lt;\/juzu.test.resources.path&gt;\n\t\t\t&lt;juzu.test.workspace.path&gt;\n\t\t\t\t${project.build.directory}\/workspace\n\t\t\t&lt;\/juzu.test.workspace.path&gt;\n\t\t&lt;\/systemPropertyVariables&gt;\n\t&lt;\/configuration&gt;\n&lt;\/plugin&gt;\n\n[...]<\/pre>\n<p>There are <strong>xml parsing library conflicts<\/strong> between htmlunit Webdriver and our eXo JCR. We\u2019ll need to add some <strong>&#8220;exclusions&#8221;<\/strong> tags into the <span class=\"navCode\">pom.xml<\/span> file. Update the <span class=\"navCode\">exo.jcr.component.ext dependency<\/span>:<\/p>\n<pre class=\"lang:default decode:true \">&lt;dependency&gt;\n\t&lt;groupId&gt;org.exoplatform.jcr&lt;\/groupId&gt;\n\t&lt;artifactId&gt;exo.jcr.component.ext&lt;\/artifactId&gt;\n\t&lt;version&gt;1.15.x-SNAPSHOT&lt;\/version&gt;\n\t&lt;scope&gt;provided&lt;\/scope&gt;\n\t&lt;exclusions&gt;\n\t\t&lt;exclusion&gt;\n\t\t\t&lt;groupId&gt;xml-apis&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;xml-apis&lt;\/artifactId&gt;\n\t\t&lt;\/exclusion&gt;\n\t\t&lt;exclusion&gt;\n\t\t\t&lt;groupId&gt;org.exoplatform.core&lt;\/groupId&gt;\n\t\t\t&lt;artifactId&gt;exo.core.component.document&lt;\/artifactId&gt;\n\t\t&lt;\/exclusion&gt;\n\t&lt;\/exclusions&gt;\n&lt;\/dependency&gt;<\/pre>\n<h2>Test configuration and mocks<\/h2>\n<p>We need to add a <strong>configuration file<\/strong> for Arquillian: <span class=\"navCode\">src\/test\/resources\/arquillian.xml<\/span>.<\/p>\n<pre class=\"lang:default decode:true \">&lt;?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?&gt;\n&lt;arquillian\n  xmlns:xsi=\"https:\/\/www.w3.org\/2001\/XMLSchema-instance\"\n  xmlns=\"https:\/\/jboss.org\/schema\/arquillian\"\n  xsi:schemaLocation=\"https:\/\/jboss.org\/schema\/arquillian https:\/\/jboss.org\/schema\/arquillian\/arquillian_1_0.xsd\"&gt;\n\n\t&lt;extension qualifier=\"webdriver\"&gt;\n\t&lt;!-- Needed for html unit web driver --&gt;\n\t\t&lt;property name=\"javascriptEnabled\"&gt;true&lt;\/property&gt;\n\t&lt;\/extension&gt;\n\n\t&lt;container qualifier=\"tomcat\" default=\"true\"&gt;\n\t\t&lt;configuration&gt;\n\t\t\t&lt;property name=\"bindHttpPort\"&gt;8080&lt;\/property&gt;\n\t\t&lt;\/configuration&gt;\n\t&lt;\/container&gt;\n\n&lt;\/arquillian&gt;<\/pre>\n<h2>Overwrite service implementation<\/h2>\n<p>We want to focus only on testing the <strong>JuZcret controller<\/strong>, not the JCR service. It\u2019s better to mock the SecretService JCR implementation. The good news is that we already have an in-memory service implementation (remember step 2). The bad news is that, today, we cannot override the service declared in <span class=\"navCode\">package-info.java<\/span> in the unit test. For now, we have a workaround, and in the future, we plan to improve the juzu-core unit test support.<\/p>\n<p>Create a mock for a secret service and add it to <span class=\"navCode\">src\/test\/java\/org\/juzu\/tutorial\/services<\/span>:<\/p>\n<pre class=\"lang:default decode:true \">package org.juzu.tutorial.services;\n\nimport javax.inject.Singleton;\n\nimport java.util.List;\nimport java.util.Set;\n\nimport org.juzu.tutorial.models.Comment;\nimport org.juzu.tutorial.models.Secret;\n\n@Singleton\npublic class SecretServiceJCRImpl implements SecretService {\n\n  private SecretService delegate;\n\n  public SecretServiceJCRImpl() {\n    this.delegate = new SecretServiceMemImpl();\n  }\n\n  @Override\n  public List&lt;Secret&gt; getSecrets() {\n    return delegate.getSecrets();\n  }\n\n  @Override\n  public void addSecret(String message, String imageUrl) {\n    delegate.addSecret(message, imageUrl);\n  }\n\n  @Override\n  public Comment addComment(String secretId, Comment comment) {\n    return delegate.addComment(secretId, comment);\n  }\n\n  @Override\n  public Set&lt;String&gt; addLike(String secretId, String userId) {\n    return delegate.addLike(secretId, userId);\n  }\n}<\/pre>\n<p>The classloader of the test will load the <strong>SecretServiceJCRImpl<\/strong> service instead of the one in the main source. This mock service <strong>delegates<\/strong> all the tasks to our in-memory implementation.<\/p>\n<p><em>Note: If you are using IntelliJ, you may get a &#8220;Duplicate class found in the file&#8221; warning because we have two instances of <strong>SecretServiceJCRImpl<\/strong> in the same package (one in <span class=\"navCode\">\/main<\/span> and one in <span class=\"navCode\">\/test<\/span>). Just ignore it.<\/em><\/p>\n<p>We also have <span class=\"navCode\">SessionProviderService<\/span> and <span class=\"navCode\">NodeHierarchyCreator<\/span>, which are the eXo JCR service in <span class=\"navCode\">package-info.java<\/span>. We don\u2019t need them for the test.<\/p>\n<p>Let\u2019s mock the <strong>eXo kernel provider<\/strong> in <span class=\"navCode\">src\/test\/java\/org\/juzu\/tutorial<\/span>:<\/p>\n<pre class=\"lang:default decode:true \">package org.juzu.tutorial;\n\nimport javax.inject.Provider;\n\nimport juzu.inject.ProviderFactory;\n\npublic class MockProviderFactory implements ProviderFactory {\n\n  @Override\n  public &lt;T&gt; Provider&lt;? extends T&gt; getProvider(final Class&lt;T&gt; implementationType) throws Exception {\n    return new Provider&lt;T&gt;() {\n      @Override\n      public T get() {\n        return null;\n      }\n    };\n  }\n}<\/pre>\n<p>Note that the <strong>provider return null instance<\/strong> is just the <strong>mock provider<\/strong> to satisfy the IOC container. We don\u2019t need a JCR service instance in the test.<\/p>\n<p>We also need to <strong>register the mock<\/strong> to the service loader by creating <span class=\"navCode\">src\/test\/resources\/META-INF\/services\/juzu.inject.ProviderFactory<\/span>:<\/p>\n<pre class=\"lang:default decode:true \">org.juzu.tutorial.MockProviderFactory<\/pre>\n<h2>Test cases<\/h2>\n<p>We decided to have a dedicated test case for each result of the tutorial steps. We\u2019ll simulate all available user interactions with the JuZcret portlet using Selenium.<\/p>\n<p><em>Note: There are still two actions that cannot simulated for now: changing the language and the portlet mode. This should be improved in a future version.<\/em><\/p>\n<p>We will develop our unit test in the <span class=\"navCode\">JuZcretTestCase.java<\/span> file in <span class=\"navCode\">src\/test\/java\/org\/juzu\/tutorial<\/span>:<\/p>\n<pre class=\"lang:default decode:true \">package org.juzu.tutorial;\n\nimport juzu.test.AbstractWebTestCase;\nimport org.jboss.arquillian.container.test.api.Deployment;\nimport org.jboss.arquillian.drone.api.annotation.Drone;\nimport org.jboss.shrinkwrap.api.spec.WebArchive;\nimport org.openqa.selenium.WebDriver;\n\npublic class JuZcretTestCase extends AbstractWebTestCase {\n\n    @Deployment(testable = false)\n    public static WebArchive createDeployment() {\n        return createPortletDeployment(\"org.juzu.tutorial\");\n    }\n\n    @Drone\n    WebDriver driver;\n\n}<\/pre>\n<p>We use the <strong>createPortletDeployment<\/strong> method from the <strong>abstract test class<\/strong> of juzu-core, which allows us to deploy our portlet in an embedded portlet container.<\/p>\n<p><strong>WebDriver<\/strong> is injected by Arquillian and help to <strong>simulate<\/strong> the <strong>user interactions<\/strong>.<\/p>\n<h2>Test rendering<\/h2>\n<p>After step 1, we have a <strong>running portlet<\/strong> that renders <span class=\"navCode\">secretWall.gtmpl<\/span>. The unit test should help to make a quick test on the results of the rendering process.<\/p>\n<pre class=\"lang:default decode:true \">import org.junit.Test;\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebElement;\n\n[...]\n\n  @Test\n  public void testRender() throws Exception {\n    driver.get(getPortletURL().toString());\n    WebElement body = driver.findElement(By.tagName(\"body\"));\n    assertTrue(body.getText().indexOf(\"JuZcret Portlet\") != -1);\n    System.out.println(driver.getPageSource());\n  }\n\n[...]<\/pre>\n<p>Our first test case is very simple:<\/p>\n<ul>\n<li>Make the request, get the html body element, and be sure that it contains the substring <strong>&#8220;JuZcret Portlet<\/strong>.<strong>&#8220;<\/strong><\/li>\n<li>Print out the whole server response to the console to see the result.<\/li>\n<\/ul>\n<h2>Test adding secret<\/h2>\n<p>After step 2, the user can <strong>add new secrets<\/strong>. Thanks to Arquillian and WebdDiver, we can easily simulate user input and submit a form in a JUnit test. Let\u2019s add this new test case for adding a secret:<\/p>\n<pre class=\"lang:default decode:true \">import org.openqa.selenium.support.ui.ExpectedCondition;\nimport org.openqa.selenium.support.ui.WebDriverWait;\n\n[...]\n\n  @Test\n  public void testSecret() throws Exception {\n    driver.get(getPortletURL().toString());\n    WebElement body = driver.findElement(By.tagName(\"body\"));\n    assertFalse(body.getText().contains(\"test secret text\"));\n\n    \/\/ add secret form\n    WebElement shareBtn = driver.findElement(By.cssSelector(\".secret-wall-heading a\"));\n    driver.get(shareBtn.getAttribute(\"href\"));\n    \/\/ input\n    WebElement secretInput = driver.findElement(By.tagName(\"textarea\"));\n    secretInput.sendKeys(\"test secret text\");\n    \/\/ submit\n    WebElement submitBtn = driver.findElement(By.tagName(\"button\"));\n    submitBtn.click();\n\n    \/\/ wait for redirecting to index page\n    body = new WebDriverWait(driver, 10).until(new ExpectedCondition&lt;WebElement&gt;() {\n      public WebElement apply(WebDriver drv) {\n        return drv.findElement(By.tagName(\"body\"));\n      }\n    });\n    assertTrue(body.getText().contains(\"test secret text\"));\n  }<\/pre>\n<ul>\n<li>We assert that there is no <strong>&#8220;test secret text&#8221;<\/strong> in the secret list.<\/li>\n<li><strong>WebDriver<\/strong> provides an <strong>API for finding elements in an html page<\/strong>. We find the URL for the add secret page.<\/li>\n<li>Find the text area and button, fill out the form, and submit it. All is written using the Java API to simulate the actions. This is a <strong>fast and clean way to conduct a UI test.<\/strong><\/li>\n<li>After submitting the add secret form, the portlet will redirect to the home page; note that it may take some time, so we need to tell WebDriver to wait until we have the response from the server by WebDriverWait<\/li>\n<\/ul>\n<h2>Test assets<\/h2>\n<p>We have tested for rendering and user interactions. In step 3, we improved the <strong>Look&amp;Feel<\/strong> portlet. We should test whether the portlet is served with the correct assets (CSS and JS files) to make sure that all our declarations for assets in package-info.java are correct:<\/p>\n<pre class=\"lang:default decode:true \">import java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n[...]\n\n@Test\n  public void testAsset() throws Exception {\n    driver.get(getPortletURL().toString());\n\n    List&lt;WebElement&gt; scripts = driver.findElements(By.tagName(\"script\"));\n    Set&lt;String&gt; srcScripts = new HashSet&lt;String&gt;();\n    for (WebElement elem : scripts) {\n    srcScripts.add(elem.getAttribute(\"src\"));\n    }\n    assertTrue(srcScripts.contains(\"https:\/\/localhost:8080\/juzu\/assets\/org\/juzu\/tutorial\/assets\/jquery\/1.10.2\/jquery.js\"));\n    assertTrue(srcScripts.contains(\"https:\/\/localhost:8080\/juzu\/assets\/juzu\/impl\/plugin\/ajax\/script.js\"));\n    assertTrue(srcScripts.contains(\"https:\/\/localhost:8080\/juzu\/assets\/org\/juzu\/tutorial\/assets\/javascripts\/secret.js\"));\n\n    WebElement style = driver.findElement(By.tagName(\"link\"));\n    assertEquals(\"https:\/\/localhost:8080\/juzu\/assets\/org\/juzu\/tutorial\/assets\/styles\/juzcret.css\",\n                   style.getAttribute(\"href\"));\n  }<\/pre>\n<p>All necessary assets should be in the server response for rendering JuZcret. This test allows us to check that all are present:<\/p>\n<p>Our portlet needs three JavaScript files:<\/p>\n<ul>\n<li><strong>js<\/strong>: This file is a juzu-core Ajax script, which provides a jquery plugin to make Ajax requests in our Juzu controller method.<\/li>\n<li><strong>js<\/strong>: JQuery is used by script.js and our portlet JS.<\/li>\n<li><strong>js<\/strong>: Our application JS file.<\/li>\n<\/ul>\n<p>The <strong>juzcret.less<\/strong> file should be compiled and served as <strong>juzcret.css<\/strong>.<\/p>\n<h2>Test Ajax actions<\/h2>\n<p>In step 5, we add some user interactions that were done using Ajax. Fortunately, HtmlUnit does a good job of <strong>simulating a browser<\/strong>. It can execute JavaScript and even Ajax actions.<\/p>\n<p><em>Note: Remember that we have enables js in arquillian.xml: <span class=\"navCode\">&lt;property name=&#8221;javascriptEnabled&#8221;&gt;true&lt;\/property&gt;<\/span><\/em><\/p>\n<p>Let\u2019s test the <strong>like feature<\/strong>:<\/p>\n<pre class=\"lang:default decode:true \">import org.openqa.selenium.support.ui.ExpectedConditions;\n\n[...]\n\n@Test\n  public void testLike() throws Exception {\n    driver.get(getPortletURL().toString());\n\n    \/\/ like\n    WebElement likeBtn = driver.findElement(By.cssSelector(\".btn-like\"));\n    likeBtn.click();\n\n    \/\/ wait\n    By selector = By.cssSelector(\".btn-like .numb\");\n    ExpectedCondition&lt;Boolean&gt; condition = ExpectedConditions.textToBePresentInElement(selector, \"1\");\n    assertTrue(new WebDriverWait(driver, 10).until(condition));\n  }<\/pre>\n<p>The test is pretty simple:<\/p>\n<ul>\n<li>Request the index page; click the Like button.<\/li>\n<li>Don\u2019t forget to wait until we have a server response; the timeout is 10 second.<\/li>\n<\/ul>\n<p>The last test is the <strong>comment feature<\/strong> test case:<\/p>\n<pre class=\"lang:default decode:true \"> @Test\n  public void testComment() throws Exception {\n    driver.get(getPortletURL().toString());\n    WebElement body = driver.findElement(By.tagName(\"body\"));\n    assertFalse(body.getText().contains(\"test comment\"));\n\n    \/\/ input\n    WebElement commentInput = driver.findElement(By.cssSelector(\".secret-add-comment\"));\n    commentInput.sendKeys(\"test comment\");\n    \/\/ submit\n    WebElement submitBtn = driver.findElement(By.cssSelector(\".btn-comment\"));\n    submitBtn.click();\n    \/\/ wait\n    ExpectedCondition&lt;Boolean&gt; condition = ExpectedConditions.textToBePresentInElement(By.cssSelector(\".secr-comments-list\"),\n                                                                                       \"test comment\");\n    assertTrue(new WebDriverWait(driver, 10).until(condition));\n  }<\/pre>\n<ul>\n<li>Check that no comment with the substring &#8220;test comment&#8221; already exists.<\/li>\n<li>Add a new comment with the message &#8220;test comment.&#8221;<\/li>\n<li>Click on the button to submit the new comment.<\/li>\n<li>Don\u2019t forget to wait until we have a server response; the timeout is 10 seconds.<\/li>\n<\/ul>\n<p>Now our <strong>JuZcret application is complete<\/strong>.<\/p>\n<p>Perform a clean install:<\/p>\n<pre class=\"lang:default decode:true \">$ mvn clean install<\/pre>\n<p>Ensure that all tests have succeeded.<\/p>\n<p><em>The final source of step 7 is available for <a href=\"https:\/\/github.com\/juzu\/portlet-tutorial\/tree\/step-7\" target=\"_blank\" rel=\"noopener\">download on Github<\/a>.<\/em><\/p>\n<p>This blog post is the last of the series of posts about Juzu.<\/p>\n<p>Apprentice, you can be proud. You are now a <strong>true Juzu developer<\/strong> with the capability to develop more and more funny Juzu applications and <strong>evangelize Juzu to everyone around you<\/strong>.<\/p>\n<p>If you have any questions, <a href=\"https:\/\/community.exoplatform.com\/portal\/g\/:spaces:juzu\/juzu\/forum\" target=\"_blank\" rel=\"noopener\">head to the Juzu forum<\/a>; we would be pleased to help you.<\/p>\n<p>If you want to contribute to Juzu, <a href=\"https:\/\/github.com\/juzu\" target=\"_blank\" rel=\"noopener\">here is the Github repo<\/a>. Please don\u2019t hesitate to contact us.<\/p>\n<p><em>And in case you missed them, here are the previous steps of the tutorial:<br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/learn-how-to-develop-great-juzu-portlets-for-exo-platform\/\">Learn how to develop great Juzu portlets for eXo Platform!<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-2-viewing-and-posting-secrets\/\">Step 2: viewing and posting secrets<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-3-building-a-sexy-secret-wall\/\">Step 3: Building a sexy secret wall<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-4-adding-likes-and-comments-for-secret\/\">Step 4: adding Likes and Comments for Secret<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-5-saving-secrets-in-the-jcr\/\">Step 5: Saving Secrets in the JCR<\/a><br \/>\n&#8211; <a href=\"https:\/\/www.exoplatform.com\/blog\/developing-juzu-portlets-step-6-let-the-whole-world-create-new-secrets\/\">Step 6: Let the whole world create new secrets<\/a><\/em><\/p>\n<p><b><a href=\"https:\/\/community.exoplatform.com\/portal\/dw\/\" target=\"_blank\" rel=\"noopener\">Join the eXo tribe<\/a> by registering for the community and get updates, tutorials, support, and access to the Platform and add-on downloads!<\/b><\/p>\n<p><!--begin adv-events--><\/p>\n<div class=\"adv-events\" style=\"background: #476fad;padding: 30px 20px;color: white\">\n<div class=\"media\">\n<div class=\"pull-right\"><a href=\"#\"><br \/>\n<img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-6587 alignright\" src=\"https:\/\/www.exoplatform.com\/blog\/wp-content\/uploads\/2014\/02\/how-to-make-the-most-of-eXo-platform41.png\" alt=\"make-the-most-out-of-eXo-platform4\" width=\"161\" height=\"85\"><br \/>\n<\/a><\/div>\n<div class=\"media-body\">\n<h4 class=\"media-heading\">Make the most out of eXo Platform 4<\/h4>\n<p>Register to the next webinar and get a complete overview of what you can do with eXo Platform 4. <strong><a href=\"https:\/\/www.exoplatform.com\/contact-us\/\">Reserve your seat now!<\/a><\/strong><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!--end adv-events--><\/p>\n","protected":false},"excerpt":{"rendered":"Previous steps: &#8211; Learn how to develop great Juzu portlets for eXo Platform! &#8211; Step 2: viewing and posting secrets &#8211; Step 3: Building a sexy secret wall &#8211; Step 4: adding Likes and Comments for Secret &#8211; Step 5: Saving Secrets in the JCR &#8211; Step 6: Let the whole world create new secrets [&hellip;]","protected":false},"author":7,"featured_media":12746,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[699],"tags":[],"lang":"en","translations":{"en":8151},"pll_sync_post":[],"_links":{"self":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/posts\/8151"}],"collection":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/comments?post=8151"}],"version-history":[{"count":0,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/posts\/8151\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/"}],"wp:attachment":[{"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/media?parent=8151"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/categories?post=8151"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.exoplatform.com\/blog\/wp-json\/wp\/v2\/tags?post=8151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}