Skip to content

"TDD is dead", he said

Jeff Schnitzer edited this page Mar 12, 2017 · 2 revisions

January 6, 2015

One of the issues that drove me out of Dropwizard was the inability to properly test code. I like deep tests that cut across a wide swath of code, with a minimum of mocking and stubbing. My favorite level to test code is the JAX-RS api itself!

Unfortunately, because Dropwizard uses Jersey to implement aspects like transaction management, you can't just casually fire up a test using an in-memory H2 database without instantiating the whole web container - and even then you can only access it using the clunky http client api. This is a mess:

public class LoginAcceptanceTest {

    @ClassRule
    public static final DropwizardAppRule<testconfiguration> RULE =
        new DropwizardAppRule<testconfiguration>(MyApp.class, resourceFilePath("my-app-config.yaml"));

    @Test
    public void loginHandlerRedirectsAfterPost() {
        Client client = new Client();

        ClientResponse response = client.resource(
            String.format("http://localhost:%d/#", RULE.getLocalPort()))
            .post(ClientResponse.class, loginForm());

        assertThat(response.getStatus()).isEqualTo(302);
    }
}

I don't want to write tests like this. And I don't want to write tests that mock and stub DAOs - half the business logic of a typical application is wound around using Hibernate (or whatnot) properly. You might as well just stub the whole test green and go home! I think this is the problem that DHH describes as TDD is dead; heavily mocked micro-tests are nowhere near as useful as whole-system tests. The problem is that we have crappy tools for making system tests (eg, all that Client gunk above).

This is the test I want:

public class LoginAcceptanceTest {
    private Injector injector;

    @BeforeMethod
    public void setUp() {
        injector = // create the injector with test modules
    }

    @Test
    public void loginHandlerRedirectsAfterPost() {
        try {
            LoginResource loginResource = injector.getInstance(LoginResource.class);
            loginResource.login(loginForm());
            assert false;
        } catch (WebApplicationException e) {
            assertThat(e.getResponse().getStatus(), equalTo(302));
        }
    }
}

But even this is wrong; an modern AJAX app doesn't issue redirects on login. Javascript submits a login form via AJAX, gets a result, and displays the appropriate response to the user. The JAX-RS test is even prettier, especially after we abstract the injector setup into a base class and include creation of the user, which is an essential part of the test!

public class LoginAcceptanceTest extends TestBase {
    @BeforeMethod
    public void setUp() {
        instance(UserCreator.class).createUser("user", "password");
    }

    @Test
    public void goodCredentialsProduceSuccessfulLogin() {
        LoginResource loginResource = instance(LoginResource.class);
        LoginResponse response = loginResource.login(new LoginRequest("user", "password"));

        assertThat(response.getToken(), isValidToken());
    }
}

The great thing about writing tests like this is that they are fully typesafe. If you refactor any of the types or methods in your IDE, your tests are included. And it's easy to write hundreds of business-level tests, just like fine-grained unit tests - but a thousand times more useful.

You can see an example of this in the GWizard example application here:

https://github.com/gwizard/gwizard-example/blob/master/src/test/java/com/example/app/resource/ThingsResourceTests.java

original