Testing JavaEE with Arquillian

intro

Next
Testing

You can’t fix what you can’t run

You can’t fix what you can’t debug

You can’t fix what you can’t test

You can’t develop if you can’t test

Testing is development

legobox
backtofuture
My application
IMG 6378
My application after some time
lego head pieces
Unit Test
garage
Integration Test

Example of CDI

PaymentGateway.java
@Inject
@CreditCard
Payment payment; (1)
1CDI injects instance
PaymentGatewayTest.java
PaymentGateway pg = new PaymentGateway();
pg.setPayment(new CreditCardPayment()); (1)
pg.doPayment(...);
1Our test is not executed within CDI container

¿Will it work in production?

Next
Principles of Arquillian

1 Portable tests

2 Executable from IDE and the build tool

3 Reuse existing framework and tools

4 Flexbible to adapt to new technologies

5 Extensible to integrate with new plataformas

6 Facilitate deployment of application

Show me the code !!!

Unit Test

PaymentGatewayTest.java
public class PaymentGatewayTest {





        PaymentGateway paymentGateway;

        @Test
        public void should_do_credit_card_payments() {
                paymentGateway = new PaymentGateway();
                paymentGateway.setPayment(new CreditCardPayment());
                assertThat(paymentGateway.doPayment(...), is(...));
        }

}

Arquillian Test

PaymentGatewayTest.java
@RunWith(Arquillian.class)
public class PaymentGatewayTest {

        @Deployment public static JavaArchive createDeployment() {
                return Shrinkwrap.create(JavaArchive.class)
                        .addClasses(PaymentGateway.class,
                                CreditCardPayment.class, CreditCard.class);
        }

        @Inject PaymentGateway paymentGateway;

        @Test
        public void should_do_credit_card_payments() {
                assertThat(paymentGateway.doPayment(...), is(...));
        }

}

Lifecycle of Arquillian

Maven (Gradle, Ant)

pom.xml
<dependencyManagement>
    <dependency>
        <groupId>org.jboss.arquillian</groupId>
        <artifactId>arquillian-bom</artifactId>
        <version>1.1.5.Final</version>
        <scope>import</scope>
        <type>pom</type>
    </dependency>
</dependencyManagement>

<dependency>
    <groupId>org.jboss.arquillian.junit</groupId>
    <artifactId>arquillian-junit-container</artifactId>
    <scope>test</scope>
</dependency>

Arquillian Test

PaymentGatewayTest.java
@RunWith(Arquillian.class)
public class PaymentGatewayTest {

        @Deployment public static JavaArchive createDeployment() {
                return Shrinkwrap.create(JavaArchive.class)
                        .addClasses(PaymentGateway.class,
                                CreditCardPayment.class, CreditCard.class);
        }

        @Inject PaymentGateway paymentGateway;

        @Test
        public void should_do_credit_card_payments() {
                assertThat(paymentGateway.doPayment(...), is(...));
        }

}
1 select a container

Choose Container (Embedded Wildfly)

pom.xml
<profile>
  <id>wildlfy-embedded</id>
  <dependencies>
    <dependency>
      <groupId>org.wildfly</groupId>
      <artifactId>wildfly-arquillian-container-embedded</artifactId>
      <version>8.1.0.Final</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</profile>

Choose Container (Embedded TomEE)

pom.xml
<profile>
  <id>tomee-embedded</id>
  <dependencies>
    <dependency>
      <groupId>org.apache.openejb</groupId>
      <artifactId>arquillian-tomee-embedded</artifactId>
      <version>1.7.1</version>
    </dependency>
  </dependencies>
</profile>
2 start container
3 package test archive

Shrinkwrap

ShrinkWrap

ShrinkWrap.create(JavaArchive.class)
    .addClasses(PaymentGateway.class)
    .addPackages(CreditCardPayment.class.getPackage());

ShrinkWrap.create(WebArchive.class)
    .addAsLibraries(x)
    .addAsWebInfResource(
                     new StringAsset("<faces-config version=\"2.0\"/>"),
                         "faces-config.xml")
    .setWebXML(new File("src/test/resources/web.xml"));

ShrinkWrap.create(EnterpriseArchive.class)
    .addAsModules(war, jar);

ShrinkWrap (Resolver)

File[] files = Maven.resolver()
                    .resolve("G:A:V").withTransitivity()
                    .asFile();

ShrinkWrap (Descriptor)

Descriptors.create(WebAppDescriptor.class)
    .metadataComplete(true)
    .version("3.0")
    .createServlet()
        .servletName(EchoServlet.class.getSimpleName())
        .servletClass(EchoServlet.class.getName()).up()
    .createServletMapping()
        .servletName(EchoServlet.class.getSimpleName())
        .urlPattern(EchoServlet.URL_PATTERN).up()
    .exportAsString()
4 run test in container
5 test result
6 disconnect container

DEMO

Next
Arquillian Extensions

Persistence
Avoid redundant code in persistence tests
Drone/Graphene
Functional tests
Byteman
Mock 2.0
Droidum
Android applications
Rest
Avoid redundant code in REST tests
Cucumber, Thucydides, Spock
Arquillian with BDD flavor

Arquillian Persistence

Arquillian Persistence

Old Way

@Before
public void preparePersistenceTest() {
  clearDatabase();
  insertData();
  startTransaction();
}
private void clearDatabase() {
  utx.begin();
  em.joinTransaction();
  em.createQuery("delete from Beer").executeUpdate();
  utx.commit();
}
private void insertData() {
  utx.begin();
  em.joinTransaction();
  Beer beer = BeerBuilder.create("Heineken").style("Pale Lager").build();
  em.persist(beer);
  utx.commit();
}

APE dependency

pom.xml
<dependency>
  <groupId>org.jboss.arquillian.extension</groupId>
  <artifactId>arquillian-persistence-dbunit</artifactId>
  <version>${arquillian-persistence.version}</version>
  <scope>test</scope>
</dependency>

Dataset

beers.yaml
----
beer:
  - id: 1
    name: Heineken
    style: Pale Lager
----

Test

BeerTest.java
@EJB BeerService beerService;

@Test
@UsingDataSet("datasets/beers.yml")
public void should_return_beer_by_type() throws Exception {
    assertThat(beerService.findBeersByType("Pale Lager"), hasSize(1));
}

DEMO

Arquillian Drone/Graphene

Arquillian Drone/Graphene

Drone/Graphene dependency

pom.xml
<dependencyManagement>
  <dependency>
    <groupId>org.jboss.arquillian.extension</groupId>
    <artifactId>arquillian-drone-bom</artifactId>
    <version>${drone.version}</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>

  <dependency>
    <groupId>org.jboss.arquillian.selenium</groupId>
    <artifactId>selenium-bom</artifactId>
    <version>${selenium.bom.version}</version>
    <type>pom</type>
    <scope>import</scope>
  </dependency>
</dependencyManagement>

Drone/Graphene dependency

pom.xml
<dependency>
  <groupId>org.jboss.arquillian.extension</groupId>
  <artifactId>arquillian-drone-webdriver-depchain</artifactId>
  <type>pom</type>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.jboss.arquillian.graphene</groupId>
  <artifactId>graphene-webdriver</artifactId>
  <version>2.0.3.Final</version>
  <scope>test</scope>
  <type>pom</type>
</dependency>

Arquillian Configuration

arquillian.xml
<arquillian>
  <extension qualifier="webdriver">
    <property name="browser">firefox</property>
  </extension>
</arquillian>

Test

SessionsTest.java
  @Deployment(testable = false)
  public static WebArchive createDeployment() {}

  @ArquillianResource
  URL contextPath;

  @Drone
  WebDriver driver;

  @Test
  public void should_show_all_sessions(@InitialPage ShowSessionPage showSessionPage) throws IOException {
    showSessionPage.assertSessionsAreShown();
  }

JSF

view.xhtml
<div class="jumbotron">
  <div id="welcomeMessage" class="content">
    <h1>List of sessions registered on system</h1>
  </div>
</div>
<div class="well well-sm">
  <div class="panel panel-default">
    <h:dataTable id="session" styleClass="table table-striped"
          value="#{sessionService.sessions}" var="sessionConference">
      <h:column>
        <f:facet name="header">Title</f:facet>
        <h:outputText styleClass="title" value="#{sessionConference.title}" />
      </h:column>
      <h:column>
        <f:facet name="header">Start</f:facet>
          #{sessionConference.startDate}
      </h:column>
    </h:dataTable>
  </div>
</div>

Page

ShowSessionPage.java
@Location("view.xhtml")
public class ShowSessionPage {

    @FindBy
    private WebElement welcomeMessage;

    @FindBy
    private WebElement session;

    public void assertSessionsAreShown() {
      assertThat(sessionTitles(), containsInAnyOrder("Devoxx", "JavaOne"));
    }
  }

DEMO

Arquillian AngularJS to Graphene

Arquillian AngularJS to Graphene

AngularJS dependency

pom.xml
<dependency>
  <groupId>org.jboss.arquillian.extension</groupId>
  <artifactId>arquillian-angularjs-graphene</artifactId>
  <version>1.2.0.Beta1</version>
</dependency>

AngularJS client

index.html
<h2>Todo</h2>
<div ng-controller="TodoCtrl">
<span>{{remaining()}} of {{todos.length}} remaining</span>
  [ <a href="" ng-click="archive()">archive</a> ]
  <ul class="unstyled">
    <li ng-repeat="todo in todos">
      <input type="checkbox" ng-model="todo.done">
      <span class="done-{{todo.done}}">{{todo.text}}</span>
    </li>
  </ul>
  <form ng-submit="addTodo()">
    <input type="text" ng-model="todoText"  size="30" placeholder="add new todo here">
    <input class="btn-primary" type="submit" value="add">
  </form>
</div>

Angular Test

AngularTest.java
@FindByNg(model = "todo.done") List<WebElement> todos;

@FindByNg(model = "todoText") WebElement todoEntry;

@FindByNg(action = "addTodo()") WebElement addTodo;

@FindByNg(repeat = "todo in todos") List<WebElement> todoRepeat;

@Test
public void testAddTodo() {
  assertEquals(2, todos.size());
  todoEntry.sendKeys("This is a new TODO item");
  addTodo.submit();
  assertEquals(3, todos.size());
}

Arquillian and BDD

Cucumber, Thucydides y Spock

Cucumber Dependency

pom.xml
<dependency>
  <groupId>com.github.cukespace</groupId>
  <artifactId>cukespace-core</artifactId>
  <version>1.5.10</version>
  <scope>test</scope>
</dependency>

Cucumber feature

conferences-service.feature
Feature: Find Conferences
  Scenario: Finding All Conferences

    Given I have Devoxx conference
    When I find all conferences in system
    Then Devoxx should be listed

Cucumber

ConferencesTest.java
@RunWith(CukeSpace.class)
@Features(value = "org/superbiz/conferences-service.feature")
public class ConferenceServiceFuntionalTest {

  @Deployment public static JavaArchive deploy() { }

  @EJB ConferenceService conferenceService;

  @Given(value = "^I have (\\w+) conference$")
  public void aConference(String conferenceName) {}

  @When(value = "^I find all conferences in system$")
  public void findAllConferences() {}

  @Then(value = "^(\\w+) should be listed$")
  public void shouldFindConferences(String conferenceName) {}
}

DEMO

Arquillian Sputnik Dependencies

pom.xml
<dependency>
  <groupId>org.jboss.arquillian.spock</groupId>
  <artifactId>arquillian-spock-container</artifactId>
  <version>${project.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.spockframework</groupId>
  <artifactId>spock-core</artifactId>
  <scope>test</scope>
  <version>${version.spock}</version>
</dependency>
<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy-all</artifactId>
  <scope>test</scope>
  <version>${version.groovy}</version>
</dependency>

Arquillian Sputnik Test

SecureServiceTest.groovy
@RunWith(ArquillianSputnik)
class AccountServiceSpecification extends Specification {

    @Deployment def static JavaArchive "create deployment"() {}

    @Inject AccountService service

    def "transfer should be possible between two accounts"() {
        when:
        service.transfer(from, to, amount)

        then:
        from.balance == fromBalance
        to.balance == toBalance

    }

Next
Conclusions

The purpose of automated testing is to enable change.

Verifying correctness is just a nice side-effect.


Jeremy Norris

Testing with mocks only proves you know how to write mocks


Dan Allen

Write Real tests

Alex Soto