In the first part of this post, I showed some ways that AspectJ would be useful in android development when it comes to testing. This final part demonstrates one way of incorporating AspectJ, and how I manage to make the aspect weaving somewhat configurable in the build process.
Android + AspectJ
A search on the internet will show that there are various ways of integrating AspectJ into the Android build process, including doing it manually or using a gradle plugin. I have been using the android-gradle-aspectj plugin for the last few years, since it has some useful features and the author seems to keep it maintained fairly well.
A basic setup for this plugin requires adding it to the buildscript repositories and dependencies blocks. I have this in build.gradle in the project root directory.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:x.x.x'
classpath 'com.archinamon:android-gradle-aspectj:x.x.x'
}
}
Then apply the plugin where required, so in the module build.gradle:
apply plugin: 'com.archinamon.aspectj'
or
plugins {
id 'com.android.application'
id 'com.archinamon.aspectj'
}
This should be enough to run a simple case, such as the example from the first post. Please see the plugin documentation for more advanced configurations.
To verify that the aspect has been compiled and weaved during the build process, you can check:
- the aspectj directory in the module build directory, containing the compiled aspect class files
- some log files generated by the plugin in the module build directory, ajc-transform.log and ajc-compile.log
- the build output log for the AspectJ plugin tasks, compile[Build variant]Aspectj and transformClassesWithAspectjFor[Build variant].
Determining When to Use Aspects or Not
If we’re only using aspects for testing, most of the time we don’t want the aspect code to be incorporated into the build process. An easy way to determine when to weave the aspect code or not is to use a Boolean condition.
For instance, in the aspect file include a Boolean condition in the pointcut to determine whether the advice will apply or not:
pointcut myPointcut(): if (aspectjEnabled) && ... // the rest of the pointcut
Here the Boolean value ‘aspectjEnabled‘ is used as a flag that will determine whether the aspect code will apply the advice.
One way of passing this flag to the aspect class is to use the generated BuildConfig class, which can be set in the build file (build.gradle) of the app.
android {
buildTypes {
debug {
// Flags for Aspect testing
buildConfigField 'boolean', 'aspectjEnabled', true
}
}
}
Then in the pointcut of the aspect file, include the flag as a conditional.
pointcut myPointcut(): if (BuildConfig.aspectjEnabled) && ... // the rest of the pointcut
Of course we can go further and set that flag from a gradle project property, rather than hardcoding it in build.gradle.
Now when the build is done, pass a project property to determine whether we want the aspect flag to be set or not.
-PaspectjEnabledProperty=true
I also have this extra bit to set the aspect flag to false if the project property was not specified (which is the default when we want to do a normal build without aspects).
ext.aspectjEnabledProperty = getAspectJEnabledFlag()
def getAspectJEnabledFlag() {
if (project.hasProperty('aspectjEnabledProperty'))
return project.property('aspectjEnabledProperty')
else
return false
}
Then in the build file for the app, the value of the aspect flag to be put into the BuildConfig class will be set dynamically.
android {
buildTypes {
debug {
// Flags for Aspect testing
buildConfigField 'boolean', 'aspectjEnabled', "${aspectjEnabledProperty}"
}
}
}
In sumary, this means that if the property ‘aspectjEnabledProperty’ is not passed to the build or is set to false, then the AspectJ weaving does not happen. Then when required, the property can be set for testing that particular bit of code.
Of course you are not restricted to one aspect flag, you can set as many flags as you like for the different tests you want to run.
-PexceptionTestProperty=true -PcrashTestProperty=false -PservicesTestProperty=true
Testing Multiple Types of Errors
In the configuration above, I’ve used a boolean value as the flag to enable/disable aspect weaving in the pointcut. Using a boolean flag is the simplest case, and probably the most common type that used.
However what about if you want to test multiple types of errors and error handling. For instance, if you want to test having the adviced method throw different types of exceptions. Another example is if you want to simulate different status codes as the response from a network call.
Then instead of using a boolean value as the flag, use an integer (or any other simple type) for example.
-PaspectjTestingProperty=1
Then in the build file:
ext.aspectTestingFlag = getAspectTestingFlag()
def getAspectTestingFlag() {
if (project.hasProperty('aspectjTestingProperty'))
return project.property('aspectjTestingProperty')
else
// default value if property not specified
return 0
}
android {
buildTypes {
debug {
// Flags for Aspect testing
buildConfigField 'int', 'aspectTestingFlag', "${aspectjTestingProperty}"
}
}
}
Then in the aspect, you can use the integer flag both as a conditional in the pointcut and as an indicator run different code in the advice.
aspect TestAspect {
pointcut myPointcut(): if (BuildConfig.aspectjTestingFlag != 0) && ... // the rest of the pointcut
int around(): myPointcut() {
switch (BuildConfig.aspectjTestingFlag) {
case 1:
// return a particular status code, or throw a particular exception type
case 2:
// return a different status code, or throw a different exception type
case 3:
// return yet another status code, or throw a third different exception type
default:
return proceed();
}
}
}
Caveats
Just a few things to be aware of:
- As already mentioned, since we can only have AspectJ compile time weaving for Android development, this means some additional build time for the AspectJ tasks.
- Unfortunately Android Studio doesn’t have IDE support for AspectJ, since it is based on the community edition of IntelliJ IDEA.
While the Ultimate version of Intellij does support AspectJ, I prefer using Android Studio.
Tip:
Sometimes to get the pointcuts correct I will copy files with the aspect, the classes I want to advice and their lib dependencies into a dummy Eclipse project so that I can use the AspectJ Development Tools (AJDT) to fine tune the pointcut. This allows me to see if the pointcuts are being applied correctly to the joinpoints I want.
- Using a Gradle plugin to handle incorporating AspectJ into the build process does make things easier. However this sometimes means having to wait for the authors of the plugin to keep up to date with the latest versions of the android gradle plugin or gradle.