Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using TDD and Progressive Enhancement to Build a Chat Application

DZone's Guide to

Using TDD and Progressive Enhancement to Build a Chat Application

In this article we show how it's possible to go from nothing to a working chat application in only 3 tests.

· Java Zone
Free Resource

Are you joining the containers revolution? Start leveraging container management using Platform9's ultimate guide to Kubernetes deployment.

I’m a strong believer in test driven design and progressive enhancement as pretty much the only way to build reliable applications with clean, maintainable code. When I started my series on trying to learn Dropwizard from the start I wanted to document how I’m using these techniques to build the application from scratch. You can consider this episode 1.5 in the series; it’s not specifically about dropwizard, but it’s there.

What’s Progressive Enhancement?

If I asked you to create a chat application, you’d probably start thinking about the design. You could ask some of the following questions

- Do I use regular HTTP or websockets?

- What database am I going to use?

- Will I support group chats?

- What will I use for authentication?

- What technology will we use in the UI?

This could go on for days, perfectly crafting a plan and design before we start executing it. And they’re certainly sensible questions, but it’s too easy to get bogged down designing the “perfect” system. As developers we know that all these good intentions tend to hit the fan as soon as we start programming. Progressive enhancement is a much more pragmatic way of developing. I use the following loop:

  • Write a test for the most basic piece of functionality that could be useful/needed

  •  Write the most basic, unoptomized code to make the test pass

  •  If needed, refactor.

  •  Repeat

This gets us straight into the code, and thinking about problems in a practical, not theoretical sense. You eventually end up with a full solution, but with a fast feedback loop and well tested code.

Practically, this means a lot of short lived code that is rewritten. If I initially build my chat application using an in-memory list for history instead of a formal back end, I’m going to have to go back and make some fairly fundamental changes. However by using TDD my code will be shaped in a way that makes this a very cheap refactoring to do, and I will have explored the problem space using code and tests, not making a manual list of things I think I’ll need to consider. Working code is the only measure of success.

For many this will be a very different way of working. I encourage you not to just read and forget this post but to try it out tomorrow at work. It’s a very satisfying way of working that results in beautiful, flexible code.

Applying This to a Chat Application

As mentioned earlier, I’m building a chat application, something like Whatsapp or telegram. What is the most basic piece of functionality we could test for in a chat application? Ideally, for one person to be able to send a message to another. To do that, we can ignore databases, authentication and all that other stuff. Let’s just send a message between two people. But we can break it down further; for this to happen, I need to be able to “see” a chat, so I need to be able to access a chats content. When a chat first starts, it will have no content in it. So,my first test should then be to retrieve a chat, and expect the chat to have no messages in it.

With progressive enhancement, the “nothing happens” test is often the first. It instantly gets you thinking about the API and design. To get to even this point, I’ve had to make a number of assumptions. I have had to decide that if two people are in the system and I request the chat log between the two of them, but they haven’t spoken, it will return an empty list of message (not null, not an error etc). And this choice is clearly documented by the test. This is why I love TDD- it results in a clear set of documentation in the form of tests. Below is the first test.

@Test

public void noChatTextForPeopleWhoHaventSpokenBefore() throws Exception {

WizChatApplication chatApplication = startServer();
HttpResponse<JsonNode> jsonResponse = get(chatApplication.url() + "/chat/jason/sookie").asJson();
assertThat(extractChat(jsonResponse), is(""));
}


private WizChatApplication startServer() throws Exception {
  WizChatApplication chatApplication = new WizChatApplication();
  chatApplication.main(new String[]{"server"});
  return chatApplication;
}



private String extractChat(HttpResponse<JsonNode> jsonResponse) {
return jsonResponse.getBody().getObject().getString("chat");
}

I’m writing the most basic test for the functionality. I’ve decided that I’m going to use REST because it’s easy to get started. If I want to change to websockets later for real time chats, I can do that later. I’ve also not used any authentication yet. That’s not important at this point. We’re just doing the basic chat functionality. We’ll write tests for auth later on when it’s important.

I’ve also gone for the most basic response, a single JSON node called “chat” which is basically just a big String with the chat in it. This isn’t sustainable long term probably; maybe it’ll end up as a list of chat message objects, specifying who sent the message, the media type etc. But for now, a string will do. Remember, we’re trying to do the smallest test to get things rolling, then will progressively enhance the code and tests as we move on.

@Path("/chat/{userOne}/{userTwo}")
@Produces(MediaType.APPLICATION_JSON)
public class ChatRoomResource {

  @GET
  public Chat chatBetween(@PathParam("userOne") Optional<String> userOne,
    @PathParam("userTwo") Optional<String> userTwo) {
    return new Chat(“”);
  }

public class Chat {
  private String chat;

  public Chat() {
  // Jackson deserialization
  }

  public Chat(String content) {
  this.chat = content;
  }

  @JsonProperty
  public String getChat() {
  return chat;
  }
}

It’s not the most exciting code, but we’re on the way to writing a chat application! Another reason I love TDD is because I can guarantee the application works- I made a number of basic errors when first coding up the implementation due to my unfamiliarity with Dropwizard and the test guaranteed I got it right before I went any further.

So far we’re fairly limited in functionality though; let’s actually have some chat text. The next test should be simple enough; if I submit a single message, it should show up in the chat.

@Test
public void chatReturnsMessagesThatHaveBeenSent() throws Exception {
WizChatApplication chatApplication = startServer();
    String message = "Hey Sookie";
    post(chatApplication.url() + "/chat/jason/sookie")
    .field("message", message)
.asJson();

    HttpResponse<JsonNode> jsonResponse = get(chatApplication.url() + "/chat/jason/sookie").asJson();

    assertThat(extractChat(jsonResponse), is("jason: " + message));

}

We’ve made another set of design decisions here; we’re using a post request with form data to send a message. The first name in the URL is the person sending the message, and the actual chat text should be preceded with {{username}}:.

None of these decisions are final. Chances are by the time we get to a scalable, realistic chat app this will be completely different. But the whole point of progressive enhancement is to start small and evolve. It forces decision to be made consciously. This results in a well designed, flexible application.

We’ve done the absolutely minimum to get the test to pass here- a solitary list of messages. By doing the least to pass we can see that clearly our tests are not up to scratch yet; that’s ok. We start with a simple test, and we refactor and improve as we go along.

I chose to make the next test cope with multiple chats. If I have conversations in two chats, can I properly retrieve them?

 @Test
    public void userWithMultipleChatsCanAccessAllChats() throws Exception {
        String message = "This a chat between bob and sue";
        post(chatApplication.url() + "/chat/bob/sue")
                .field("message", message)
                .asJson();

        String message2 = "This is a chat between Mike and Bob”;

        post(chatApplication.url() + "/chat/mike/bob")
                .field("message", message2)
                .asJson();

        HttpResponse<JsonNode> jsonResponseBobSue = get(chatApplication.url() + "/chat/bob/sue").asJson();
        HttpResponse<JsonNode> jsonResponseMikeDan = get(chatApplication.url() + "/chat/mike/bob").asJson();

        assertThat(extractChat(jsonResponseBobSue), is("bob: " + message));
        assertThat(extractChat(jsonResponseMikeDan), is("mike: " + message2));

    }

Unsurprisingly, this test fails with our solitary list of messages. We can now start to think about a more sensible implementation.

There’s a number of routes you can go down, but the important thing is to go simple. We’re not talking about persistence, searching or anything significant yet. As a result we just want a simple in memory solution. Initially I tried to go really dumb with a map of maps, but in the end I found that it was much easier to use a list of chat objects.

@Path("/chat/{userOne}/{userTwo}")
@Produces(MediaType.APPLICATION_JSON)
public class ChatRoomResource {

    List<Chat> chats = new LinkedList<Chat>();

    @GET
    public Chat chatBetween(@PathParam("userOne") final Optional<String> userOne,
                            @PathParam("userTwo") final Optional<String> userTwo) {
        String userOneName = userOne.get();
        String userTwoName = userTwo.get();
        return chats.stream()
                .filter(chat -> chat.isBetween(userOneName, userTwoName))
                .findFirst()
                .orElse(new Chat("", userOneName, userTwoName));
    }

    @POST
    public void newMessage(
            @PathParam("userOne") Optional<String> from,
            @PathParam("userTwo") Optional<String> to,
            @FormParam("message") Optional<String> message
    ) {
        String formattedMessage = from.get() + ": " + message.get();
        for (Chat chat : chats) {
            if (chat.isBetween(from.get(), to.get())) {
                chat.addMessage(formattedMessage);
                return;
            }
        }
        chats.add(new Chat(formattedMessage, from.get(), to.get()));

    }

}

public class Chat {
    private String chat;
    public String userOne;
    public String userTwo;

    public Chat() {
        // Jackson deserialization
    }

    public Chat(String content, String userOne, String userTwo) {
        this.chat = content;
        this.userOne = userOne;
        this.userTwo = userTwo;
    }


    @JsonProperty
    public String getChat() {
        return chat;
    }

    public void addMessage(String s) {
        chat += s;
    }

    public boolean isBetween(String s, String s1) {
        return userOne.equals(s) && userTwo.equals(s1) || userOne.equals(s1) && userTwo.equals(s);
    }
}

In three tests we’ve moved to a functioning chat application. Although we’ve no UI, it’s possible to use this as a functioning app by using Postman to submit our chats.

Image title

Image title

Image title

There’s a lot more tests and a lot more code to write, but hopefully this has given you an insight into using progressive enhancement as a way to explore requirements and develop robust code.

The chat app code is available here on github, but I’m working on it actively so you can see how I progress.

Moving towards a private or Hybrid cloud infrastructure model? Get started with our OpenStack Deployment Models guide to learn the proper deployment model for your organization.

Topics:
java ,tdd ,dropwizard ,rest api

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}