For Java projects I sometimes use AspectJ for various tasks, but for Android projects I initially decided against using it.
There were various reasons for that:
- The structure of an Android project, and its build process, was already a lot more complicated and slower than for generic Java projects.
- For Android development, only compile time weaving is supported for AspectJ, which makes it less useful.
- The Android Studio IDE does not have support for AspectJ.
However I eventually found some uses for aspects when it comes to testing. Another reason I decided to give AspectJ a try was the availability of Android AspectJ plugins for Gradle. They make integrating AspectJ into the Android build process easier, I use this one.
Using aspects I can weave test code into the application code when required for a particular test. Then when no longer needed, I would just do a normal build without the aspect weaving, and the test code is kept totally isolated in the aspect file.
I found this to be a good alternative to what sometimes happens during testing:
- adding test code for a particular test, and then having to remove it or comment it out afterwards
- adding temporary logging to find a bug, profile some code, etc
What is it Good For, Some Examples
Testing Crashlytics
without AspectJ:
When testing for Crashlytics, the Firebase documentation recommends adding a temporary button with a click handler that crashes the app.
with AspectJ:
Instead I can weave the crash inducing code into any existing app event I like, e.g a button click, menu click, etc.
Testing exception handling
without AspectJ:
Write some temporary test code to throw an exception to see if the app handles it correctly.
with AspectJ:
Have an aspect that throws the exception from any method I choose. I can also have the pointcut advice multiple methods so that the test exception can be thrown from multiple locations.
Testing interactions with external services
(For instance, checking that Google Play services are available.)
without AspectJ:
Write some test code to simulate the response from the external service that you want to test for. For example, when checking if Google Play services are available then you might want to test that your app correctly handles an unsuccessful result code such as SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED, etc.
with AspectJ:
Write an aspect that advices where you call the external service, and have it return the result that you want to test for. It’s up to you whether you want to actually make the call to the external service in the advice using proceed(..) or just have the advice return the result you want.
The Usual Stuff, like Tracing
One of the most common uses of AspectJ is for method tracing, this can be useful for profiling the app or when trying to track down that hard-to-find bug.
without AspectJ:
Add temporary trace logging to the app code that you think might need profiling or be causing a bug.
with AspectJ:
Enable tracing of method calls with logging for any or all method calls that I want by adjusting the pointcut for of a tracing aspect. It’s quick and easy to write a pointcut to advice all methods in a class, for example.
There are plenty of examples of AspectJ tracing aspects around, or you can use android tools to generate trace logs by instrumentation.
An AspectJ Example
Let’s start with a common scenario, a service class with a method that makes a network call (presumably to get some data and apply business logic to it). This service method would be called by an activity or fragment in the app.
Note that this example assumes that you are already familiar with how to program in AspectJ native syntax.
public class BusinessService { public Result serviceNetworkCall() { return ... // return some data from a network call } }
I want to test that the activity/fragment correctly handles the situation where there is an error when the service method is called. Here is an aspect that can be used to simulate an exception being thrown on the method call.
aspect TestExceptionInjector { pointcut injectTestException(): target(BusinessService) && execution(public Result *(..)); Result around(): injectTestException() { // can allow the adviced method to run first before the exception is thrown by calling <em>proceed()</em> first, in case we want some side effects to occur // simulate an exception being thrown from the method(s) adviced throw new RuntimeException("TEST"); } }
In this aspect the pointcut will advice the service method, and throws an exception instead its normal execution. Then we can run the app to check that the Activity / Fragment that calls the service method does handle this error properly.
I can also do this where a reactive library, such as RxJava, is being used in the service class.
public class BusinessServiceRx { public Observable<Result> serviceNetworkCallRx() { return ... // return some data from a network call } public Observable<Result> anotherServiceNetworkCallRx() { return ... // return some data from a network call } } privileged aspect TestExceptionInjectorRx { pointcut injectTestExceptionRx(): target(BusinessServiceRx) && execution(public Observable<Result> *(..)); Observable<Result> around(): injectTestExceptionRx() { // simulate an exception being thrown downstream from the method(s) adviced return Observable.error(new RuntimeException("TEST")); } }
This example also shows that the pointcut in the aspect can advice multiple service methods.
Determining When to Build with Aspects (or Not)
Next, we need to weave the aspect in the example into the app code, and then find some way to determine when to do a build with aspects for testing, and when to just do a normal build. In the concluding part of this post, I will show the workflow I use as one way of doing this.
For Testing Only?
As I have mentioned, when using aspects the test/logging code resides in an aspect file that is isolated from the app code and should never get into a release build. However it is always available when I want to run the tests again, and there is no need to worry about forgetting to remove temporary test code after the testing is done.
The use of AOP (Aspect Oriented Programming) for testing only is purely my personal preference. Of course you can just as easily use AspectJ as part of your development process and include aspects in your app code.