How to test a REST API with Arquillian

Testing a REST API on a real application server with Arquillian is easy – if you know what you need to do 😉

I have this simple REST service, that authenticates a user:

@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response authenticateUser(final UserData userData)
{
    ...
    return Response
        .status(401)
        .entity(false)
        .build();
}

Let’s write a test for this REST API, that uses a real application server and calls the interface from the outside.

Arquillian dependencies

The REST extensions for Arquillian make it fairly easy to test a deployed web application. Let’s start with their dependency in gradle.build:

// main BOM for Arquillian
'org.jboss.arquillian:arquillian-bom:1.1.11.Final',
// JUnit container
'org.jboss.arquillian.junit:arquillian-junit-container:1.1.11.Final',
// Chameleon (or any other specific container, e.g. JBoss, Wildfly etc.)
'org.arquillian.container:arquillian-container-chameleon:1.0.0.Alpha6',
// REST extensions
'org.jboss.arquillian.extension:arquillian-rest-client-impl-jersey:1.0.0.Alpha4'

Arquillian test

I can now write a test against the REST interface on the server like this:

@Deployment
public static WebArchive createDeployment()
{
    return ShrinkWrap
        .create(WebArchive.class)
        .addPackages(true, Filters.exclude(".*Test.*"),
            SessionResource.class.getPackage(),
            ...)
        .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}

@Test
@RunAsClient
public void authenticateUser(
    @ArquillianResteasyResource final WebTarget webTarget)
{
    final Response response = webTarget
        .path("/sessions")
        .request(MediaType.APPLICATION_JSON)
        .post(Entity.json(new UserData(
            "myuser",
            "mypassword")));
    assertEquals(true, response.readEntity(Boolean.class));
}

Note the @RunAsClient. This tells Arquillian to run the test as a client against the remote server and not within the remote server. The test class isn’t even deployed to the application server in this case (see Filters.exclude(".*Test.*")). That’s exactly what I needed, because I wanted to test the REST API from the outside, to make sure it’s available to its clients. Alternatively, you could set the whole deployment to @Deployment(testable = false), instead of configuring each test individually.

The REST extensions for Arquillian now call the test method with a WebTarget as a parameter. It points directly to the URL of the deployed web application, e.g. http://1.2.3.4:8080/044f5571-3b1a-4976-9baa-a64b56f2eec1/rest. So you don’t have to manually create the target URL everytime.

JBoss server configuration

It took me a while to find out how the target server – JBoss EAP 7 in my case – needs to be configured, to make this test pass, because the initial error message after my first attempt to run the test wasn’t very helpful at all:

javax.ws.rs.ProcessingException: java.net.ConnectException: Connection refused: connect
    at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:287)
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:255)
    ...
Caused by: java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)

I could see in the server’s logs, that the deployment of the WAR file into JBoss was successful, so it had to be the REST client, that couldn’t connect to the API. A simple System.out.println(webTarget.getUri()); showed the problem: http://0.0.0.0:8080/044f5571-3b1a-4976-9baa-a64b56f2eec1/rest. Arquillian uses the IP configuration of the target server and I had configured JBoss to listen on any of its interfaces (hence 0.0.0.0).

To fix this problem I made JBoss listen on the exact public IP address of the server by adding this to \standalone\configuration\standalone.xml. Be sure to use the real IP address and not 0.0.0.0 or <any-address />.

<interfaces>
    <interface name="management">
        <inet-address value="1.2.3.4"/>
    </interface>
    <interface name="public">
        <inet-address value="1.2.3.4"/>
    </interface>
</interfaces>

12 thoughts on “How to test a REST API with Arquillian”

  1. Excellent post!

    Just FYI, It’s more efficient to use @Deployment(testable=false) when running only client tests, as oppose to using @RunAsClient. As far as running, they do the same, but if you use @Deployment(testable=false) Arquillian will leave the Deployment alone and not spend time bundling it up for in-container execution. Also, since no in-container is prepared, Arquillian will assume RunAsClient as the only option so no need to use the annotation explicitly.

  2. Hi Aslak,

    thanks for the feedback. In fact, in my client-only tests I already switched to testable=false instead of repeating @RunAsClient for every test method. However, it’s good to know that this also speeds up the tests!

    Best regards,
    Stefan

  3. Hi Stefan and Aslak,

    Could you please suggest how can i write test for secured rest api?

    @Path("/data_conf")
    @Produces(MediaType.APPLICATION_JSON)
    public class DataResource {

    @POST
    @Path("/getData")
    @Secured("data_policy")
    public Response getData(@Context SecurityContext context,
    @NotNull(message = "Payload shouldn't not be null")
    Configuration configurationData) {
    ...
    }

    }

    The SecurityContext context is injected after AuthenticationFilter logic.
    Like…

    @Secured
    @Provider
    @Priority(Priorities.AUTHENTICATION)
    public class AuthenticationFilter {...}

    And my test looks like this…
    —-TEST—-

    @Test
    @RunAsClient
    public void testGetData(@ArquillianResteasyResource("data_conf") ResteasyWebTarget webTarget) {

    Configuration request = getConfiguration();

    Response response = webTarget
    .path("/getData")
    .request(MediaType.APPLICATION_JSON).header("Authorization", TOKEN)
    .post(Entity.json(request));

    ....
    }

    But i always get

    DEBUG [org.apache.http.impl.conn.DefaultClientConnection] Receiving response: HTTP/1.1 405 Method Not Allowed

  4. Hi Xilibit,

    unfortunately, I haven’t used secured APIs, yet. However, your error message points in the direction of a wrong HTTP method. Are you sure the path to your resource is correct?

    Best regards,
    Stefan

  5. Thanks for response!
    I think the api path is right. If i deploy my artifact to the wildfly and use the Postman to test it – it works as expected.

  6. Hi, I got an error – do you haven an idea ?
    BR

    “java.lang.RuntimeException: Could not configure Chameleon container
    default … Caused by:
    org.jboss.arquillian.container.spi.ConfigurationException:
    chameleonTarget must be provided in format server:version:type at
    org.arquillian.container.chameleon.ChameleonConfiguration.validate(ChameleonConfiguration.java:47)
    at
    org.arquillian.container.chameleon.InitiateContainer.initiateChameleon(InitiateContainer.java:66)
    … 49 more

  7. Hi Stephen,

    I don’t know this exact error, but it seems as if something in the configuration has the wrong format. Did you search your config file for “chameleon”?

    Best regards,
    Stefan

  8. Hi,
    Thanks for the useful guide,
    Could I ask to put the complete source code here?
    I have troubles configuring my rest api.

  9. Hi, and many thanks for your useful guide.
    I used it in my project but when calling request, I get the error : “java.lang.ClassCastException: org.apache.cxf.jaxrs.client.spec.ClientImpl$WebTargetImpl cannot be cast to org.glassfish.jersey.client.JerseyWebTarget”
    Do you know why it’s creating ClientImpl insead of JerseyClient?

  10. Hi SaharSadat, I haven’t used Arquillian for quite a while now, because I switched to Testcontainers. Arquillian was always pretty hard to set up and the tests were also quite slow. Therefore, I test my applications with Docker now and got rid of Arquillian completely.

  11. @Stefan Macke, my problem was resolved by upgrading Wildfly and Jboss. Thanks a bunch for your reply.

Leave a Comment

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax