{#========================================== Docs : "Testing" ==========================================#}

Testing

Spincast provides some nice testing utilities. You obviously don't have to use those to test your Spincast application, you may already have your favorite testing toolbox and be happy with it. But those utilities are heavily used to test Spincast itself, and we think they are an easy, fun, and very solid testing foundation.

First and foremost, Spincast testing is about testing using a Guice context. In some cases, a regular JUnit unit test (with some mocking) may be more than enough. But using a Guice context to run the tests is so easy and powerful that we practically don't use any regular unit test in Spincast!

Here's a quick introduction to how Spincast testing works:

In other words, there are two types of Spincast testing:

Of course, when you run some tests against a real application, you first have to mock some parts. Tests must be without side effects! The classic example is a database: a test database must be used when tests are run or otherwise it would be total craziness. Another example is that you may want to disable some initialization tasks your application runs when it starts, so the tests run faster. The Spincast testing utilities and pattern make this "mocking" super easy, by allowing you to provide an overriding Guice module. Your real application is started but you have the opportunity to tweak some of its bindings before the tests are run. We'll see how in the next sections. But, first, let's quickly look at how to install the Spincast testing utilities...

{#========================================== Installation ==========================================#}

Installation

Add this Maven artifact to your project to get access to the Spincast testing utilities:

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-testing-default</artifactId>
    <version>{{spincastCurrrentVersion}}</version>
    <scope>test</scope>
</dependency>

If you really don't want any dependencies to be pulled from the spincast-default artifact, and even if only the test scope is affected here, you can use the spincast-testing-core artifact instead. Note that, in that case, you won't have access to the SpincastDefaultGuiceModule module to create a testing Guice context, though.

Also, it's good to know that the Spincast JUnit Runner, at which we'll have a look in a next section, can easily be used outside a Spincast application. It has its own artifact, org.spincast:spincast-testing-junit-runner:{{spincastCurrrentVersion}}, and has no dependency to any other Spincast artifacts.

{#========================================== Integration test example ==========================================#}

Integration test example

Let's write an integration test from scratch, to see how Spincast testing utilities work!

For this example, we will use the Quick Start application and we will write a test to validate one of its API methods. If you look at the App class from the Quick Start, you will see that a POST("/sum") route is defined. This is the route we're going to test.

This route expects a POST request with two post parameters:

The response will be the sum of the two numbers, wrapped inside a Json object:

{"result":"42"}

Before we start writing the test class, let's have another look at the App class, the main class of the Quick Start application. Notice that the main(...) method is not the one that actually creates the Guice context and starts the application: it delegates that job to a createApp(...) method:

// The main method
public static void main(String[] args) {
    createApp(args, null);
}

// The createApp method
public static Injector createApp(String[] args, Module overridingModule) {

    if(args == null) {
        args = new String[]{};
    }

    //==========================================
    // Should we override the base app modules
    // with an overring module?
    //==========================================
    Injector guice = null;
    if(overridingModule != null) {
        guice = Guice.createInjector(Modules.override(getAppModules(args))
                                            .with(overridingModule));
    } else {
        guice = Guice.createInjector(getAppModules(args));
    }

    App app = guice.getInstance(App.class);
    app.start();

    return guice;
}

This indirection may seem trivial at first, but we will see that it is at the very root of the integration testing Spincast suggests.

We'll come back to this createApp(...) method in a moment but, for now, notice that in addition to creating the Guice context and starting the application, this method also accepts an overridingModule module and returns the application's Guice injector. When the application starts in production, using its main(...) method, no overridingModule module is used and the returned Guice injector is simply ignored. But things will be different inside our integration test classes!

Let's now start writing the test class.

Writing the test class

Let's call our test class "QuickStartSumTest", and this is going to be its skeleton:

public class QuickStartSumTest {

    @Test
    public void validRequest() throws Exception {
        // TODO
    }
    
    // Other tests...
    
}

As you see, we are going to implement only one test in this example: validRequest(). This test will validate that a valid request returns a valid response. Of course, in real life, we should also test for invalid requests, for edge cases scenarios, etc.

Now, are we going to implement this test to validate a web API?

Without using integration testing as Spincast allows, we may have to:

Wouldn't it be nicer it we could test all those parts together, as they will actually be run in production? Without mocking and hacking? Well, this is exactly the kind of integration testing that Spincast provides! You start your real application, as it would be started in production, and you run your tests against it... Let's see how!

First, let's make our test class extend SpincastIntegrationTestBase, which is the base class Spincast provides for integration testing:

public class QuickStartSumTest extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {
        // TODO
    }

    @Test
    public void validRequest() throws Exception {
        // TODO
    }
    
    // Other tests...

}

Explanation :

The SpincastIntegrationTestBase only requires one thing from us: that we provide the Guice injector to use to run the test class. So it is now time to come back to the famous createApp(...) method from our Quick Start application! Do you remember what this method does? It starts the application but it also returns the Guice injector. So let's start our application and retrieve its Guice injector:

public class QuickStartSumTest extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {

        Module overridingModule = getDefaultOverridingModule(IAppRequestContext.class, 
                                                             IAppWebsocketContext.class);
        return App.createApp(null, overridingModule);
    }

    @Test
    public void validRequest() throws Exception {
        // TODO
    }
    
    // Other tests...

}

Explanation :

At this point, our test class is ready and we can start implementing the "validRequest()" test itself. We'll soon start to see the benefits of the setup we just put into place!

Writing the test

In the test we're about to write, we'll need an instance of the IJsonManager component to validate the Json response returned by the server. With the setup we put into place, it is very easy: we now have full access to the application's Guice context. Also, dependencies are automatically injected into the test class (thanks to the SpincastIntegrationTestBase base class). Therefore, to get an instance of the IJsonManager component, we simply inject it!

public class QuickStartSumTest extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {

        Module overridingModule = getDefaultOverridingModule(IAppRequestContext.class, 
                                                             IAppWebsocketContext.class);
        return App.createApp(null, overridingModule);
    }
    
    @Inject
    private IJsonManager jsonManager;

    @Test
    public void validRequest() throws Exception {
        // TODO
    }
    
    // Other tests...
    
}

Isn't this cool? Think about it! Our real (but tweakable) application is started, and we can write our tests the exact same way we write the application itself: with full dependency injection and with a running HTTP server so we can test the web API.

Let's now write the test...

The SpincastIntegrationTestBase base class provides methods to easily use a Spincast HTTP Client instance. We will use this client to create and send a request to the "/sum" route that we want to test.

public class QuickStartSumTest extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {

        Module overridingModule = getDefaultOverridingModule(IAppRequestContext.class, 
                                                             IAppWebsocketContext.class);
        return App.createApp(null, overridingModule);
    }
    
    @Inject
    private IJsonManager jsonManager;

    @Test
    public void validRequest() throws Exception {
    
        IHttpResponse response = POST("/sum").addEntityFormDataValue("first", "40")
                                             .addEntityFormDataValue("second", "2")
                                             .addJsonAcceptHeader()
                                             .send();

        // TODO : the assertions
    }
    
    // Other tests...
    
}

Explanation :

Let's now add some assertions to validate the response:

public class QuickStartSumTest extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {

        Module overridingModule = getDefaultOverridingModule(IAppRequestContext.class, 
                                                             IAppWebsocketContext.class);
        return App.createApp(null, overridingModule);
    }
    
    @Inject
    private IJsonManager jsonManager;

    @Test
    public void validRequest() throws Exception {
    
        IHttpResponse response = POST("/sum").addEntityFormDataValue("first", "40")
                                             .addEntityFormDataValue("second", "2")
                                             .addJsonAcceptHeader()
                                             .send();

        assertEquals(HttpStatus.SC_OK, response.getStatus());
        assertEquals(ContentTypeDefaults.JSON.getMainVariationWithUtf8Charset(),
                     response.getContentType());

        String content = response.getContentAsString();
        assertNotNull(content);

        IJsonObject resultObj = this.jsonManager.create(content);
        assertNotNull(resultObj);

        assertEquals(new Integer(42), resultObj.getInteger("result"));
        assertNull(resultObj.get("error", null));
    }
    
    // Other tests...
    
}

Explanation :

And that's it! Here's the final test class, with a few more test examples. This class is actually part of the Quick Start application source.

Using a custom base class

Instead of having to implement the "createInjector()" method in each test class we create, let's create a custom base class:

public abstract class AppIntegrationTestBase extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {

        Module overridingModule = getDefaultOverridingModule(IAppRequestContext.class, 
                                                             IAppWebsocketContext.class);
        return App.createApp(null, overridingModule);
    }

    protected String[] getMainArgs() {
        return null;
    }
}

Now, our test classes simply have to extend this base class:

public class QuickStartSumTest extends AppIntegrationTestBase {

    @Test
    public void validRequest() throws Exception {
        // ...
    }
    
    // Other tests...

}

Make sure you read the following section, Writing an overriding module for an example of a better custom base class!

Finally, note that if you use the Quick Start as a start for your application, a base class has already been created for you: AppIntegrationTestBase. And here's an example of a test class using it: QuickStartIntegrationTest

Writing an overriding module

As we saw, we can provide an overriding module when we start the application. This allows us to mock some components so our tests run without side effects. For example, we could change an IUserDatabase binding so it points to a TestUserDatabase implementation instead of pointing to the real database.

An important thing to know is that this overriding module must contain a binding for the Spincast Http Client with WebSocket support since the SpincastIntegrationTestBase base class uses this client intensively.

If you use the default overriding module (the one returned by getDefaultOverridingModule(...)), then this binding is already done. But if you need to use a custom overriding module, you have to make sure you include it otherwise you'll get a binding exception when you'll try to run your tests.

In fact, the recommended way of creating a custom overriding module is by always combining it with the default one:

public abstract class AppIntegrationTestBase extends 
        SpincastIntegrationTestBase<IAppRequestContext, IAppWebsocketContext> {

    @Override
    protected Injector createInjector() {
        return App.createApp(getMainArgs(), getOverridingModule());
    }

    protected String[] getMainArgs() {
        return null;
    }

    protected Module getOverridingModule() {

        Module defaultOverridingModule = 
                getDefaultOverridingModule(IAppRequestContext.class,
                                           IAppWebsocketContext.class);

        Module appOverridingModule = new AbstractModule() {

            @Override
            protected void configure() {

                // your tests bindings...
            }
        };

        return Modules.combine(defaultOverridingModule, appOverridingModule);
    }
}

Explanation :

{#========================================== Custom context test example ==========================================#}

Custom context test example

In the previous example, we used integration testing: we started our real application and we used its Guice context and HTTP server to run our test. But for some tests, starting an HTTP server is simply overkill.

In those cases, the tests that we are going to write will be closer to "unit tests". But those tests will still be a little bit different than traditional unit tests: they are going to use a Guice context. By doing so, those tests will be able to use dependency injection and "mocking" some components will be very easy.

Let's see an example of this kind of tests by looking at a real Spincast test class: JsonObjectsTest.java. As you can see, this test class extends SpincastTestBase instead of SpincastIntegrationTestBase and doesn't start any application. Instead, the test class creates the Guice injector/context by itself:

public class JsonObjectsTest extends SpincastTestBase {

    @Override
    protected Injector createInjector() {
        return Guice.createInjector(new SpincastDefaultTestingModule());
    }
    
    //...
}

Explanation :

Exactly as we were able to do in the Integration test example, we can inject any dependency we need in this test class:

public class JsonObjectsTest extends SpincastTestBase {

    @Inject
    protected IJsonManager jsonManager;

    @Override
    protected Injector createInjector() {
        return Guice.createInjector(new SpincastDefaultTestingModule());
    }
    
    //...
    
    @Test
    public void fromJsonString() throws Exception {

        String str = "{\"innerObj\":{\"someBoolean\":true,\"someInt\":123}," +
                     "\"anotherBoolean\":true,\"anotherInt\":44444}";

        IJsonObject jsonObj = this.jsonManager.create(str);
        assertNotNull(jsonObj);
        assertEquals(true, jsonObj.getBoolean("anotherBoolean"));
        assertEquals(new Integer(44444), jsonObj.getInteger("anotherInt"));

        IJsonObject jsonObj2 = jsonObj.getJsonObject("innerObj");
        assertNotNull(jsonObj2);
        assertEquals(true, jsonObj2.getBoolean("someBoolean"));
        assertEquals(new Integer(123), jsonObj2.getInteger("someInt"));

        String jsonString = jsonObj.toJsonString();
        assertEquals(str.length(), jsonString.length());
    }
    
    //...
}

Explanation :

Ant that's it: unit testing with a Guice context!

{#========================================== Spincast JUnit runner ==========================================#}

Spincast JUnit runner

Spincast's testing base classes all use a custom JUnit runner: SpincastJUnitRunner.

This custom runner has a couple of differences as compared with the default JUnit runner, but the most important one is that instead of creating a new instance of the test class before each test, this runner only creates one instance.

This way of running the tests works very well when a Guice context is involved. The Guice context is created when the test class is initialized, and then this context is used to run all the tests of the class. If integration testing is used, then the application is started when the test class is initialised and both its Guice context and its HTTP server are used to run all the tests of the class.

Let's see in more details how the Spincast JUnit runner works:

Since the Guice context is shared between all the tests of a test class, you have to make sure you reset everything that may be required before running a test. To do this, used JUnit's @Before annotations. If you use the SpincastTestBase base class or a class extending it, you can also override the existing beforeTest() and afterTest() method without the need for annotations.

Spincast JUnit runner features

Spincast JUnit runner provides those features:

A quick note about the @Repeat annotation: this annotation should probably only be used for debugging purpose! A test should always be reproducible and should probably not have to be run multiple times. But this annotation, in association with the testFailure(...) method, can be a great help to debug a test which sometimes fails and you don't know why!

{#========================================== SpincastTestBase ==========================================#}

The SpincastTestBase class

The SpincastTestBase class is the root of all Spincast testing base classes. As we saw in the Custom context test example section, it is also the class to extend if you want to run some tests using a Guice context but without starting your actual application.

Some information about the SpincastTestBase class:

{#========================================== SpincastIntegrationTestBase ==========================================#}

The SpincastIntegrationTestBase class

As we saw in the Integration test example, SpincastIntegrationTestBase is the class to extend to run integration tests using Spincast. It is used when you want to run tests against your real application.

Some information about the SpincastIntegrationTestBase class:

{#========================================== Other test base classes ==========================================#}

Other test base classes

SpincastTestBase and SpincastIntegrationTestBase are the most important base classes Spincast provides for testing, but there are some others. We will briefly introduce them here.

SpincastNoAppIntegrationTestBase

The SpincastNoAppIntegrationTestBase class extends SpincastNoAppIntegrationTestBase. It can be used when you need a running HTTP server and all the HTTP utilities provided by the SpincastNoAppIntegrationTestBaseclass, but you don't want to start your application.

The Guice context is, in that case, created using a module you provide by implementing the abstract getTestingModule() method.

The server is automatically started in the beforeClass() method.

All routes are deleted before each test is run.

SpincastDefaultNoAppIntegrationTestBase

The SpincastDefaultNoAppIntegrationTestBase class extends SpincastNoAppIntegrationTestBase. It simply parameterizes it with the default route context type and WebSocket context type. You should probably not have to use this class if you have custom context types (which is recommended and already done if your application is based on the Quick Start).