Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you override Play framework controller in a unit testing context?

I am creating a basic POST JSON api endoint. I would like to unit test it, and want to make sure I am doing it appropriately in the Play framework. So far I am using Guice for dependency injection and JUnit for my unit testing library.

Here is my controller code:

public class NotificationController extends Controller {

  private RabbitQueueService _rabbitQueueService;

  @Inject
  public NotificationController(RabbitQueueService service) {
    _rabbitQueueService = service;
  }

  @BodyParser.Of(BodyParser.Json.class)
  public Result post() {
    ObjectMapper mapper = new ObjectMapper();
    Notification notification;

    try {
      JsonNode notificationJsonNode = Controller.request().body().asJson();
      notification = mapper.readValue(notificationJsonNode.toString(), 
                                      Notification.class);
      _rabbitQueueService.push(notification);
      return Results.created(notificationJsonNode, "UTF-8");

    } catch (JsonParseException e) {
      e.printStackTrace();
    } catch (JsonMappingException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }

    return Results.badRequest();
  }
}

My RabbitQueueService code:

public class RabbitQueueService {

  private Channel _channel;
  private Connection _connection;

  public RabbitQueueService() {
      ConnectionFactory factory = new ConnectionFactory();
    factory.setHost(Config.RABBITMQ_HOST);
    try {
      _connection = factory.newConnection();
      _channel = _connection.createChannel();
      _channel.queueDeclare(Config.RABBITMQ_QUEUE, false, false, false, null);
      _channel.exchangeDeclare(Config.RABBITMQ_EXCHANGE, "fanout");
      _channel.queueBind(Config.RABBITMQ_QUEUE, Config.RABBITMQ_EXCHANGE, "");
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  public void push(Notification notification) {
    try {
      _channel.basicPublish(Config.RABBITMQ_EXCHANGE, "", null, notification.getBytes());
        _channel.close();
        _connection.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void pop() {

  }
}

My MockQueueService code:

public class MockQueueService extends RabbitQueueService {

  @Override
  public void push(Notification notification) {
    /* Do nothing because you know... thats what I do */
  }

  @Override
  public void pop() {
    /* Do nothing because you know... thats what I do */
  }
}

and finally my current unit test code:

public class ApplicationTest {

    @Test
    public void addMessageToQueue() {
      running(fakeApplication(), new Runnable() {
          public void run() {
              FakeRequest request = new FakeRequest("/POST", "/api/v1/notifications");
              Notification notification = new Notification(UUID.randomUUID(), 
                                                           new NotificationType(UUID.randomUUID(), 
                                                                                "Critical"), 
                                                           "Test notification message");
        try {
                  ObjectMapper mapper = new ObjectMapper();
                  String json = mapper.writeValueAsString(notification);
                  JsonNode node;
          node = mapper.readTree(json);
                  request.withJsonBody(node);
        } catch (IOException e) {
          e.printStackTrace();
        }
              route(request);
          }
        });
    }
}

This all works fine when making a curl request to test my endpoint through play run. My main question is: how do I use the MockQueueService in my unit test? I don't see anyway to do it with fakeApplication() helper. I could instantiate it directly like

NotificationController nc = new NotificationController(new MockQueueService());
nc.post();

but the problem is I need to override the body of the play request with an appropriate request body and I think I need a FakeRequest for that.

Any help, samples, or advice would be helpful.

UPDATE

I have posted a gist example with the necessary example files. The things specifically that I did to get it working:

  • Setup a new GlobalUnitTest file that I passed into the fakeApplication helper
  • Changed NotificationController to be a singleton. This allowed me to pull in the NotificationController instance so I could check the QueueService count as part of the assertion.
like image 949
Eric LaForce Avatar asked Nov 10 '22 12:11

Eric LaForce


1 Answers

FakeApplication takes a bunch of arguments that you could use to inject your new service. You could use a combination of any of these:

  • additionalPlugins
  • additionalConfiguration
  • withGlobal

They each let you specify some additional configuration you could use only during testing. Another thing you could do is have a separate Global object just for testing, that is used to create your controllers. The Global object is used to return your controller instance when you use @ in your route definition. Then, you can create a separate application.test.conf that refers to GlobalTest that is loaded when you run play test.

like image 139
Jamie Avatar answered Nov 14 '22 22:11

Jamie