I am looking at AspectJ to see if perhaps we can use it in our test suite.
We have a rather large third party Java communications library hardwired to use its own classes (which do not implement any interfaces) which in turn mean that we need a physical backend present and correctly configured to be able to run tests.
I am looking at our options for removing this restriction. A possibility would be to create a subclass of the troublesome classes and then ask AspectJ to simply replace "new X" with "new OurSubclassOfX" when loading the third party library, but I am new to AspectJ and from my brief skimming of the documentation this is not a typical use case.
Can AspectJ do this? What would the configuration snippet be?
Yes, this is possible. Let us assume you have a hard-wired class, possibly fetching something from a database, and want to mock it via an aspect:
package de.scrum_master.aop.app;
public class HardWired {
private int id;
private String name;
public HardWired(int id, String name) {
this.id = id;
this.name = name;
}
public void doSomething() {
System.out.println("Fetching values from database");
}
public int getSomething() {
return 11;
}
@Override
public String toString() {
return "HardWired [id=" + id + ", name=" + name + "]";
}
}
Then there is a little driver application using that very class (not an interface):
package de.scrum_master.aop.app;
public class Application {
public static void main(String[] args) {
HardWired hw = new HardWired(999, "My object");
System.out.println(hw);
hw.doSomething();
System.out.println(hw.getSomething());
}
}
The output is as follows:
HardWired [id=999, name=My object]
Fetching values from database
11
Now you define your derived mock class which should replace the original for testing purposes:
package de.scrum_master.aop.mock;
import de.scrum_master.aop.app.HardWired;
public class HardWiredMock extends HardWired {
public HardWiredMock(int id, String name) {
super(id, name);
}
@Override
public void doSomething() {
System.out.println("Mocking database values");
}
@Override
public int getSomething() {
return 22;
}
@Override
public String toString() {
return "Mocked: " + super.toString();
}
}
And finally you define an aspect with a simple pointcut and advice to replace the original value during each constructor call:
package de.scrum_master.aop.aspect;
import de.scrum_master.aop.app.HardWired;
import de.scrum_master.aop.mock.HardWiredMock;
public aspect MockInjector {
HardWired around(int p1, String p2) : call(HardWired.new(int, String)) && args(p1, p2) {
return new HardWiredMock(p1, p2);
}
}
The output changes as desired:
Mocked: HardWired [id=999, name=My object]
Mocking database values
22
You do that once per class and constructor and are fine. In order to generalise the approach you would need joinpoint properties and, depending on how far you want to go, maybe reflection, but this here is pretty straightforward. Enjoy!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With