== Container Object pattern If you search for a description of what *Page Object* is, you'll find that it describes a pattern that allows you to model content in a reusable and maintainable way. The description also points that within your web app's UI, there are areas that your tests interact with, and a Page Object simply models those areas as objects within the test code. Using the Page Object pattern reduces the amount of duplicated code. If the UI changes, only the Page Object will need to be updated for the test cases to run. As you can see, the Page Object pattern applies to UI elements. We (the Arquillian community) have coined a new pattern following Page Object that we call the *Container Object* pattern. You can think of a Container Object as a mechanism to encapsulate areas (data and actions) related to a container (for now only Docker containers) that your test might interact with. For example: * the host IP where the container is running * the bounded port for a given exposed port * any parameter configured inside the configuration file (Dockerfile), like a user or password to access the service running in the container. For example, if running a MySQL database in a container, it could be the user and password to access the database. Notice that nothing prevents you from generating the correct URL to access the service directly from your test, or executing commands against the container, for example retrieving an internal file. As is the case with Page Objects, the Container Object pattern gives you a mechanism to build a model that can be reused on several projects. Before looking at how this pattern is implemented in Cube, let's go through an example: Suppose all your applications need to send a file to an FTP server. To write an integration/component test for your apps, you might need an FTP server to send the file to, and then check that the file was correctly sent. One way to implement such test is (i) by using Docker to start an FTP server just before executing the test, then (ii) execute the test using this docker container as the receiving FTP server, (iii) before stopping the container check that the file is present, and finally (iv) stop and destroy the container. All these operations that involve the FTP server and container could be encapsulated inside a Container Object. This container object might contain the following information: * which image is used * IP and port bound to the host where the FTP server is running * user and password required to access the FTP server * methods for asserting the existence of a file inside the container Test code will only interact with this object instead of directly hard coding all required information inside the test. Again, as in the Page Object pattern, any change on the container only affects the Container Object and not the test itself. Now let's see how _Arquillian Cube_ implements the Container Object pattern. === Arquillian Cube and Container Object Let's see a simple example of how you can implement a Container Object in _Cube_. Suppose you want to create a container object that encapsulates a ping pong server running inside Docker. The Container Object will be a simple POJO with special annotations: [source, java] .PingPongContainer.java ---- package org.superbiz.containerobject; @Cube(value = "pingpong", portBinding = "5000->8080/tcp") // <1> @CubeDockerFile public class PingPongContainer { @HostIp // <2> String dockerHost; @HostPort(8080) private int port; public URL getConnectionUrl() { // <3> try { return new URL("http://" + dockerHost + ":" + port); } catch (MalformedURLException e) { throw new IllegalArgumentException(e); } } } ---- <1> The `@Cube` annotation configures the Container Object <2> A Container Object can be enriched with Arquillian enrichers <3> The Container Object hides how to connect to the PingPong server The `@Cube` annotation is used to configure a Container Object. Its `value` property is used to specify how the started container will be named (in this example `pingpong`) while the `portBinding` property can be used to specify the port binding information for the container instance (in this case `5000->8080/tcp`). Notice that `portBinding` can also accept an array, in which case more than one port binding definitions can be specified. The next annotation is `@CubeDockerFile`, which configures how the container is created. This example will use a Dockerfile located at the default class path location. As the default location is the _package+classname_, in our example the `Dockerfile` should be placed at `org/superbiz/containerobject/PingPongContainer`. It is possible to set any other class path location by passing it as the `value` property of the `@CubeDockerFile` annotation. IMPORTANT: The `@CubeDockerFile` annotation defines the _location_ where the `Dockerfile` is found, not the file itself. This location must be reachable from the ClassLoader creating the container object, which means it should be on the class path for the class loader to be able to find it. Any Cube can be enriched with any client side enricher. In the previous example a `@HostIp` enricher is used, but it could be enriched similarly with `@CubeIp` (which works similar to `@HostPort`), or a `DockerClient` instance if the field is annotated with `@ArquillianResource`. Finally the `@HostPort` is used to translate the exposed port to the bound port. In this example the port value will be 5000. Later you will learn briefly why this annotation is important. After creating the container object, you can start using it in your test: [source, java] .PingPongTest.java ---- @RunWith(Arquillian.class) public class PingPongTest { @Cube PingPongContainer pingPongContainer; @Test public void shouldReturnOkAsPong() throws IOException { String pong = ping(); assertThat(pong, containsString("OK")); assertThat(pingPongContainer.getConnectionPort(), is(5000)); } } ---- The most important step in that test example is setting the Container Object as a field of the test class, and annotating it with `@Cube`. Before running a test, Arquillian will detect that it needs to (i) start a new Cube (Docker container), (ii) create the Container Object and (iii) inject it in the test. Notice that this `@Cube` annotation is exactly the same as the one used when you defined the Container Object. Placing a `@Cube` annotation on the field will allow you to override any property of the Container Object from the test side. Due to the override mechanism, it is important to use the `@HostPort` annotation when the bound port is needed, since it can be changed from the test definition. IMPORTANT: The Container Object pattern only works in Client mode or Arquillian Standalone. ==== ShrinkWrap Dockerfile Descriptor If you want you can use *ShrinkWrap Dockerfile* descriptor to create the `Dockerfile` file. First thing you need to do is adding `shrinkwrap-descriptord-api-docker dependencies`: [source, xml] .pom.xml ---- org.jboss.shrinkwrap.descriptors shrinkwrap-descriptors-api-docker test org.jboss.shrinkwrap.descriptors shrinkwrap-descriptors-impl-docker test ---- And in similar way you use `@Deployment` in Arquillian test, you can use `@CubeDockerFile` annotation in a public static method to define `Dockerfile` file and elements required for creating the container programmatically: [source, java] .PingPongContainer.java ---- @Cube(value = "pingpong", portBinding = "5000->8080/tcp") public class PingPongContainer { @CubeDockerFile // <1> public static Archive createContainer() { // <2> String dockerDescriptor = Descriptors.create(DockerDescriptor.class).from("jonmorehouse/ping-pong").exportAsString(); return ShrinkWrap.create(GenericArchive.class) .add(new StringAsset(dockerDescriptor), "Dockerfile"); // <3> } } ---- <1> `@CubeDockerFile` annotation is used. <2> Method must be `public` and `static`. <3> Returns a `GenericArchive` with all elements required for building the Docker container instance. As part of Arquillian Cube, we are providing a `org.arquillian.cube.impl.shrinkwrap.asset.CacheUrlAsset` asset. This asset is similar to `org.jboss.shrinkwrap.api.asset.UrlAsset`, but the former caches to disk for an amount of time the content that has been downloaded from the URL. By default this expiration time is 1 hour, and it is configurable by using the proper constructor. ==== Links A Container Object can reference more Container Objects from inside of it. Effectively, a Container Object can be an aggregation of other Container Objects: [source, java] .FirstContainerObject.java ---- @Cube public class FirstContainerObject { @Cube("inner") // <1> LinkContainerObject linkContainerObject; } ---- <1> Cube definition inside another Cube. In this case Arquillian Cube will create a link from `FirstContainerObject` to `LinkContainerObject` with link value `inner:inner`. Of course you can override the link value using `@Link` annotation. [source, java] ---- @Cube("inner") @Link("db:db") TestLinkContainerObject linkContainerObject; ---- ==== Image So far, you've seen that a Container Object creates an instance from a `Dockerfile` using `@CubeDockerFile` annotation. You can also create a Container Object from an image by using `@Image` annotation: [source, java] .ImageContainerObject.java ---- @Cube("tomme") @Image("tomee:8-jre-1.7.2-webprofile") public static class ImageContainerObject { } ---- In this case Arquillian Cube will create containers based on the image defined in the annotation. ==== Environment You can set environment variables using `@Environment` annotation at field or object level: [source, java] ---- @Environment(key = "C", value = "D") @Environment(key = "A", value = "B") @Image("tomee:8-jre-1.7.2-webprofile") public class ImageContainer { } ---- ==== Volume You can set environment variables using `@Volume` annotation at field or object level: [source, java] ---- @Image("tomee:8-jre-1.7.2-webprofile") @Volume(hostPath = "/mypath", containerPath = "/containerPath") @Volume(hostPath = "/mypath2", containerPath = "/containerPath2") public class ImageContainer { } ---- ==== Creating container objects dynamically Up to this point you have seen how to automatically inject Container Objects in tests. Arquillian Cube also allows creating container objects from code. In the next example, the original `PingPongTest` has been rewritten to create the Container Object inside the test method. [source, java] .PingPongTest.java ---- @RunWith(Arquillian.class) public class PingPongTest { @ArquillianResource ContainerObjectFactory factory; // <1> @ArquillianResource CubeController cubeController; @Test public void shouldReturnOkAsPong() throws IOException { PingPongContainer pingPongContainer = factory.createContainer(PingPongContainer.class); // <2> try { String pong = ping(); assertThat(pong, containsString("OK")); assertThat(pingPongContainer.getConnectionPort(), is(5000)); } finally { cubeController.stop("pingpong"); // <3> cubeController.destroy("pingpong"); } } } ---- <1> A `ContainerObjectFactory` instance is injected into the test. <2> The injectect factory instance is used to instantiate a container object. <3> A `CubeController` could be used to stop the associated docker container. Although declaring container objects as fields of a test class is preffered, as it offers better control of the lifecycle of the container, creating container objects dynamically allows controlling exactly in which moment in time and in which order the containers are created. === Arquillian Cube and Container Object DSL Another option is using a generic container objects provided by Arquillian Cube to generate cube instances. Using this approach you gain in velocity at time of writing the definition, but on the other side it is harder to reuse them or providing custom operations like you can do in custom container objects. With DSL you can generate container objects or network objects as well. ==== Container Objects DSL To create a generic container object you only need to create a field of type `org.arquillian.cube.docker.impl.client.containerobject.dsl.Container` and annotate it with `@DockerContainer`. Now you need to provide the full definition of the container using the DSL. Let's see how to use it following the previous Ping Pong example. [source, java] .PingPongTest.java ---- @DockerContainer // <1> Container pingpong = Container.withContainerName("pingpong") // <2> .fromImage("jonmorehouse/ping-pong") .withPortBinding(8080) .build(); @Test public void should_return_ok_as_pong() throws IOException { String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080)); // <3> assertThat(response).containsSequence("OK"); } ---- <1> Annotate field with `DockerContainer` <2> Start the DSL using `withContainerName` method <3> You can get Docker Host Ip address and binding port for given exposed port TIP: In case you define more than one generic container and want to start them in a specfic order, there is `order` attribute in annotation to specify it. The higher the number, the sooner the container is started. IMPORTANT: Container objects by default are started and stopped in class scope. To change it to method scope you can use `.withConnectionMode` call. ===== JUnit Rule If you are using `JUnit` and want to use Container DSL builder approach, you can use `JUnit Rule` instead of `Arquillian Runner`. To do use it you need to define the container object using an special `JUnit Rule`. [source, java] .RedisTest.java ---- @ClassRule public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6") .withPortBinding(6379); Jedis jedis = new Jedis(redis.getIpAddress(), redis.getBindPort(6379)); jedis.set("foo", "bar"); ---- IMPORTANT: You need to add `org.arquillian.cube:arquillian-cube-docker-junit-rule` dependency. In both approaches (Runner and JUnit Rule) of Container Object DSL way, star operator is supported. [source, java] ---- @ClassRule public static ContainerDslRule redisStar = new ContainerDslRule("redis:3.2.6", "redis*") .withPortBinding(6379); ---- ===== JUnit 5 If you are using `JUnit 5` and want to use Container DSL approach, you can use `JUnit 5 Extension` instead of `Arquillian Runner`. To do use it you need to define the container object using `ContainerDsl` class and registering `ContainerDslResolver`. [source, java] .RedisTest.java ---- include::../docker/ftest-docker-junit5/src/test/java/org/arquillian/cube/docker/junit5/RedisTest.java[tag=docs, indent=0] ---- If field is static, container is started and stopped just once. If not then it is started and stopped for each test method execution. IMPORTANT: You need to add `org.arquillian.cube:arquillian-cube-docker-junit5` dependency. ==== Network Objects DSL To create a network using DSL approach you only need to create a field of type `org.arquillian.cube.docker.impl.client.containerobject.dsl.Network` and annotate it with `org.arquillian.cube.docker.impl.client.containerobject.dsl.DockerNetwork`. Let's see how to create a new network with default driver. [source, java] .NetworkTest.java ---- @DockerNetwork // <1> Network network = Network.withDefaultDriver("mynetwork").build(); // <2> ---- <1> Annotate field with `DockerNetwork` <2> Start the DSL using `withDefaultDriver` method ===== JUnit Rule If you are using `JUnit` and want to use Network DSL builder approach, you can use `JUnit Rule` instead of `Arquillian Runner`. To do use it you need to define the network object using an special `JUnit Rule`. [source, java] .NetworkTest.java ---- @ClassRule public static final NetworkDslRule network = new NetworkDslRule("mynetwork"); ---- IMPORTANT: You need to add `org.arquillian.cube:arquillian-cube-docker-junit-rule` dependency. ===== JUnit 5 If you are using `JUnit 5` and want to use Network DSL approach, you can use `JUnit 5 Extension` instead of `Arquillian Runner`. To do use it you need to define the network object using `NetowrkDsl` class and registering `NetworkDslResolver`. [source, java] .NetworkTest.java ---- include::../docker/ftest-docker-junit5/src/test/java/org/arquillian/cube/docker/junit5/NetworkTest.java[tag=docs, indent=0] ---- If field is static, network is created and destroyed just once. If not then it is created and destroyed for each test method execution. IMPORTANT: You need to add `org.arquillian.cube:arquillian-cube-docker-junit5` dependency. == Arquillian Standalone and Cube You can use Arquillian Standalone with Arquillian Cube too. Arquillian Standalone is a mode of Arquillian which allows you to use Arquillian but without deploying any application. Basically it means no `@Deployment` static method, and tests runs as client implicitly. Running Arquillian Cube in Standalone mode means that Arquillian Cube starts all defined containers in the correct order. Internally Arquillian Cube implicitly defines the `autostartContainers` property (unless you define it), with `regexp:.*` expression, which means all containers will be created/started. If you want to avoid this behavior, you can set this property to `[none]`. As a result, Arquilian Cube will not auto-start any container and you will be responsible of starting manually each instance (using for example, the CubeController class) by your own. Dependencies you need to set for Standalone mode are: [source, xml] .pom.xml ---- org.jboss.arquillian.junit arquillian-junit-standalone test org.arquillian.cube arquillian-cube-docker test ---- <1> You need to change `arquillian-junit-container` to `standalone`.