Testing SecureSocial

August 5, 2013
Play! Play Testing Authentication SecureSocial

I started using the Play2 framework in conjunction with the SecureSocial authentication module. One of the features of the SecureSocial module is that it allows for Play controller actions to be annotated with @SecuredAction which ensures that the user is authenticated before the action is executed.


For e.g,

public class MeetingsController extends Controller {

private static final Form<meeting> meetingForm = Form.form(Meeting.class);

@SecureSocial.SecuredAction
public static Result newMeeting() {
    return ok(views.html.meetings.details.render(meetingForm, "[]"));
}

}


I started encountering problems when trying to test controllers using the framework.

For e.g,
public class MeetingsControllerTest {

@Test
public void shouldReturnNewMeeting() {
    Result result = callAction(routes.ref.MeetingsController.newMeeting());
    assertThat(status(result)).isEqualTo(OK);
}

}


The first error I got was something along the lines of the application not being started.


Caused by: java.lang.RuntimeException: There is no started application
[error]     at scala.sys.package$.error(package.scala:27)
[error]     at play.api.Play$$anonfun$current$1.apply(Play.scala:51)
[error]     at play.api.Play$$anonfun$current$1.apply(Play.scala:51)
[error]     at scala.Option.getOrElse(Option.scala:120)
[error]     at play.api.Play$.current(Play.scala:51)
[error]     at securesocial.core.Authenticator$.cookieName$lzycompute(Authenticator.scala:188)
[error]     at securesocial.core.Authenticator$.cookieName(Authenticator.scala:188)
[error]     at securesocial.core.Authenticator$.(Authenticator.scala:201)
[error]     at securesocial.core.Authenticator$.(Authenticator.scala)



So wrapping the test in a fake application got rid of that error but….



    @Test
    public void shouldReturnNewMeeting() throws Exception {
        running(fakeApplication(), new Runnable() {
            @Override
            public void run() {
                Result result = callAction(routes.ref.MeetingsController.newMeeting());
                assertThat(status(result)).isEqualTo(OK);
            }
        });
    }

led to another problem


[error] Test MeetingsControllerTest.shouldReturnNewMeeting failed: expected:<[200]> but was:<[303]>
This was caused by the application wanting to authenticate the user which is to be expected. So after a bit of googling the best viable solution was this post on Stackoverflow http://stackoverflow.com/questions/16244541/play-securesocial-developer-environment-and-unit-testing. Basically suggesting to remove the annotation before running the test using javassist.

So finally my end unit test looked like



    @Test
    public void shouldReturnNewMeeting() throws Exception {
        removeAnnotationsFromMethodsInClass(“controllers.MeetingsController”);
        running(fakeApplication(), new Runnable() {
            @Override
            public void run() {
                Result result = callAction(routes.ref.MeetingsController.newMeeting());
                assertThat(status(result)).isEqualTo(OK);
            }
        });
    }

private static void removeAnnotationsFromMethodsInClass(String clazz) {
    try {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(clazz);
        CtMethod[] methods = cc.getDeclaredMethods();
        for (CtMethod method : methods) {
            AnnotationsAttribute attribute = (AnnotationsAttribute) method.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag);
            if (attribute != null) {
                attribute.setAnnotations(new Annotation[]{});
                method.getMethodInfo().addAttribute(attribute);
            }
        }
        cc.writeFile();
        cc.toClass();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Admittedly, the remove annotations method is very crude as it just strips any method with an annotation off and can definitely be refined.

The test does run and pass. However I’m not entirely sure if there are any adverse ramifications of doing this. So this is what I’m sticking to until I find a better solution.