Use AOP Aspects as Mocks in JUnit tests?

This post illustrates a subtle relationship between Mock Objects and Aspect Oriented Programming. Some code examples are shown, and ends with questions for further research. This is a follow on to a prior “Using AspectJ For Testing Legacy Code“. Author: Josef Betancourt

Use AOP Aspects as Mocks in JUnit tests?

Written by:
Josef Betancourt
Date:
2013-05-29
Subject:
AOP vs Mocks for testing

Categories and Subject Descriptors
D.2.2 [Software Engineering]: Design Tools and Techniques, Object-Oriented programming

Keywords
interceptors, AOP, Mock Objects, JMockit, JUnit, Aspectj, unit test

Introduction
In Unit testing during code development and maintenance, the smallest unit of code is tested in isolation from collaborating units or subsystems. In well designed systems, testing is much simpler: outgoing interfaces are easier to manage. In legacy systems and/or badly written code, that may not be the case. Further, when testing legacy systems, changes to increase testability are not always possible.

Thus, various patterns, frameworks, and tools are used to support this isolation. Mock Object frameworks are ideal for this. Aspect Oriented Programming (AOP) is also capable of providing this isolation but is rarely mentioned. What are the differences? Is one better than another for testing?

Aspect Oriented Programming
See the wikipedia entry for more information.
In the Java world, AspectJ is the most well-known AOP implementation.

Mock Objects
Mock Objects are simulated objects that mimic the behavior of real objects in controlled ways.

The JMockit toolkit is a modern Java mocking toolkit. One distinguishing feature of JMockit is that it is powerful enough to Mock legacy unmockable code.

Example Project
A Client class uses a Service object’s query method to get a user’s name: Client.getUserName(int) –> Service.query(int).

For the test we want to substitute the name for a specific argument. When invoked with argument integer 1, the result is the string “second”, but in the test we want to return “Hello world!”. This example is just to show the technology; a hello world app, not practical in itself.

Advice.
To intercept a method call on an object in AOP an Aspect is created.

“Aspects wrap up pointcuts, advice, and inter-type declarations in a modular unit of crosscutting implementation.”
 

The Advice below is written in the annotation based AspectJ syntax introduced in AspectJ 5.

@Aspect
static class TestAspect {
		
	@Pointcut("call( * AspectJUnit.Service.query(int)) && args(i)")		
	void intercept(int i){}
		
	@Around("intercept(i)")
	public String query(ProceedingJoinPoint tjp, int i){
	    return (i == 1) ? "Hello world!"
		: (String) tjp.proceed(new Object[]{i});
	}		
	
}
 

This defines a method that will ‘replace’ the original target. The terminology used by the AspectJ programming manual:

• A Join Point is a well-defined point in the program flow.
• A Pointcut picks out certain join points and values at those points.
• An Advice is code that is executed at a join point.
 

AspectJ defines many Join Points. There is even a Handler execution join point: “When an exception handler executes. Handler execution join points are considered to have one argument, the exception being handled”.

The pointcut language is very rich and the signature patterns can be generic or very specific.

We can also use an anonymous pointcut instead:

@Aspect
static class TestAspect {
		
	@Around("call( * AspectJUnit.Service.query(int)) && args(i)")
	public String doQuery(ProceedingJoinPoint jp, int i){
	    return (i == 1) ? "Hello world!"
		: (String) jp.proceed(new Object[]{i});
	}		
	
}
 

Note that the advice, here doQuery(..), does not have to match the original target method’s name.

Mocking
In JMockit‘s State Based API an object can be mocked by using a MockUp class. Within that class any method that needs to be mocked is re-implemented and annotated with @Mock.

Since JMockit allows even private, final, or static methods to be overridden, this is not normal Java method overriding.

class MockService extends MockUp<Service> {
	@Mock
	public String query(Invocation invocation, int n) {
		return (n == 1) ? "Hello world!"
			: ((Service) invocation.
			getInvokedInstance()).query(n);
	}			
}
 

We can also define the mock using an anonymous inline class within the test method:

new MockUp<Service>() {				
        @Mock
	public String query(Invocation invocation, int n) {
	     return (n == 1) ? "Hello world!" 
              : ((Service) invocation. 
                getInvokedInstance()).query(n);
	}
};
 


Comparison
Structure and Syntax
Compare this to the prior Aspect. They are both a class definition. The new behavior is also a method. The difference is the pointcut. In JMockit the pointcut and the advice specification are combined. Note that the method signature must match the target method’s. In the AspectJ Aspect, these are separate.

@Aspect
static class TestAspect {
		
	@Around("call( * AspectJUnit.Service.query(int)) && args(i)")
	public String doQuery(ProceedingJoinPoint jp, int i){
	    return (i == 1) ? "Hello world!"
		: (String) jp.proceed(new Object[]{i});
	}		
	
}
class MockService extends MockUp<Service> {

	@Mock
	public String query(Invocation invocation, int n) {
		return (n == 1) ? "Hello world!"
			: ((Service) invocation.
			getInvokedInstance()).query(n);
	}			
}
 

The combined syntax in the Mock approach is simpler, and since a test is usually targeted at one specific method, the extra AOP pointcut expressiveness may have limited use. The Mock class can even be created in the test inline. A paper referenced below even makes the suggestion that test based pointcut definitions are an improvement to AOP since they are less fragile.

The aspect must be created as a static class and the associated advice is instrumented at compile time. The mock is integrated in the JUnit run time so it’s ‘advice’ is active only for the current test, thus, you have more opportunities for various tests of the same target. The full source code of the test using Aspects does not have as many assertions as the one written using Mocks because of this early instrumentation. See section below for listings.

There are more differences. These are of course due to the intended application of these two technologies. AOP is a more general purpose systems tool. Crosscutting concerns are an architectural artifact whereas isolation is a testing concern.

Mocks are a subset of Aspects
AOP is optimized for cross-cutting concerns. But, collaborator isolation is just an example of a single-use test-based cross-cutting concern. So Mocks are just a special use of general AOP capabilities.Mocks supply a single joinpoint, and in many implementations allow the use of an Around advice. This has been sufficient and with other features such as Expectations, the Mock Objects framework fits well into current testing approaches.

In AOP there is a richer language for selecting different kinds of joinpoints. And the pointcuts are used by different kinds of advice, such as cflow, before, after, around, and so forth. The modularization (inheritance and other features) into Aspects presents an opportunity for more high-level syntax and use, especially with the annotation based language.

Can Mock Objects use AOP techniques?
This begs the question of whether Mock Objects can or should use generic AOP techniques? If so how?

One example of the integration of AspectJ is the Spring framework, which now can use the AspectJ pointcut language in its own AOP implementation. Spring’s implementation is not specifically targeted as a test solution.

Another technology is the use of a language that can create “advice” rules and be invoked via instrumentation. This is available in Byteman. Byteman uses Event Condition Action (ECA) rules.

More recent versions of JMockit now supports a ‘wildcard’ mock method: ‘”Object $advice(Invocation)”, which if defined will match every method in the mocked class hierarchy.’ — mockit Annotation Type Mock

If AOP pointcuts do have a use-case in testing mocks, could this be an area where a pointcut DSL could be introduced in future?

 

Aspects and Mocks shown in full listings

Example Mocks in a JUnit test
package com.octodecillion.jmockit.example;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.List;

import mockit.Invocation;
import mockit.Mock;
import mockit.MockUp;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * mockit.Invocation class use example
 */
public class JMockItExample2 {

	/**  the client we are testing */
	public class Client {
		private Service service;
		
		public Client(Service service) {
			this.service = service;
		}
		
		/** invoke the 'expensive' service. */
		public String getUserName(int n){
			return service.query(n);			
		}
		
	}
	
	/** the service that does integration stuff */
	public class Service {
			/**
			 * @param key 
			 * @return 
			 */
			public String query(int n) {				
				return values.get(n);
			}
			
			private final List<String> values = 
				Arrays.asList("first","second","third");
	}
	
	/**
	 * Nested test class.
	 *
	 */
	@RunWith(JUnit4.class)
	public static class UnitTest{
		private JMockItExample2 example;
		
		@Before
		public void before(){
			example = new JMockItExample2();
		}
		
		/**   */
		@Test
		public void should_do_around_advice() {
			String expected = "second";
			String actual = example.new Service().query(1);
			assertThat(actual, is(expected));
			
			new MockUp<Service>() {				
				/**
				 * Use an Invocation to allow use of mocked object.
				 */
				@SuppressWarnings("unused")
				@Mock
				public String query(Invocation invocation, int n) {
					return (n == 1) ? "Hello world!"
						: ((Service) invocation.
						getInvokedInstance()).query(n);
				}
			};
			
			expected = "Hello world!";
			actual = example.new Service().query(1);
			assertThat(actual, is(expected));
			
			// do the around advice
			actual = example.new Client(example
					.new Service()).getUserName(1);
			
			assertThat(actual, is(expected));			

		}		

	}

}

 
Example Aspect in a JUnit test
/**  */
package com.octodecillion.jmockit.example;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Using AspectJ in a JUnit test.
 * <p>
 * For compile issue in Eclipse: {@link http://stackoverflow.com/questions/8958267/java-lang-verifyerror-expecting-a-stackmap-frame}
 * @see http://stackoverflow.com/questions/8958267/java-lang-verifyerror-expecting-a-stackmap-frame
 * 
 */
public class AspectJUnit {

	/**  the client we are testing */
	public class Client {
		private Service service;
		
		public Client(Service service) {
			this.service = service;
		}
		
		/** invoke the 'expensive' service. */
		public String getUserName(int n){
			return service.query(n);			
		}
		
	}
	
	/** the service that does integration stuff */
	public class Service {
			/**
			 * @param key 
			 * @return 
			 */
			public String query(int n) {				
				return values.get(n);
			}
			
			private final List<String> values = 
				Arrays.asList("first","second","third");
	}
	
	@Aspect
	static class TestAspect {
		
		@Around("call( * AspectJUnit.Service.query(..)) && args(i)")
		public String doQuery(ProceedingJoinPoint tjp, int i){
			return (i == 1) ? "Hello world!"
				: (String) tjp.proceed(new Object[]{i});
		}		
		
	}
	
	/**
	 * Nested test class.
	 *
	 */
	@RunWith(JUnit4.class)
	public static class UnitTest{
		private AspectJUnit example;
		
		/**   */
		@Test
		public void should_do_around_advice() {
			String expected = "Hello world!";
			String actual = example.new Service().query(1);
			assertThat(actual, is(expected));
			
			actual = example.new Client(example
					.new Service()).getUserName(1);
			
			assertThat(actual, is(expected));			

		}
		
		@org.junit.Before
		public void setup(){
			example = new AspectJUnit();
		}

	}	

}

 

Related Reading

Similar Posts:

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

One thought on “Use AOP Aspects as Mocks in JUnit tests?”

Leave a Reply

Your email address will not be published. Required fields are marked *