/* Copyright (c) 2005 Per S Hustad. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of surrogate.sourceforge.net nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.sf.surrogate.core;
import net.sf.surrogate.core.SurrogateManager;
/**
* Abstract aspect for use with the Surrogate framework. This aspect provides
* the standard interaction with the
* {@link net.sf.surrogate.core.SurrogateManager}. Instead of coding relatively
* complex advices, the user only needs to create a concrete implementation of
* the <code>SurrogateCalls</code> advice, and implement the <a
* href=#mockPointcut()"> <code>mockPointcut</code> </a> pointcut. The advices
* then handles the standard processing towards the
* <code>SurrogateManager</code>.
* <p>
* An example implementation is
* <pre>
* aspect MyCalls extends SurrogateCalls {
*
* protected pointcut mockPoincut() :
* (
* execution(* com.mycompany.MyClass.getOrder(..) ) ||
* call (java.io.*Writer.new(..)) ||
* call(* java.lang.System.currentTimeMillis())
* ) ;
* }
* </pre>
* and an example "difficult-to-test or mock" java class is:
* <pre>
* package com.mycompany;
* import java.io.*;
*
* public class MyClass {
*
* public static final Order getOrder(String orderId) {
* Writer out = new BufferedWriter(
* new FileWriter("D:/production/customer_name.log"));
* out.write(orderId);
* return new Order(System.currentTimeMillis());
* }
* ....
* }
* </pre>
* Since the method is final and contains calls to external, varying entities
* (system time and external file), it is normally difficult to 1) replace the
* "getOrder" method by a mock and 2) test the internals of the method.
* Surrogate provides a solution to this problem.
*
* The advice would, when woven with the "classes-under-test", allow you to
* substitute mock objects for the following:
* <ul>
* <li>Every time <code>com.mycompany.MyClass.getOrder</code> executes. You
* could override the method by a corresponding "mock method" using the
* {@link net.sf.surrogate.core.SurrogateManager#addMockMethod addMockMethod},
* or override the method execution and return value by using the
* {@link net.sf.surrogate.core.SurrogateManager#addMock addMock} method.
* <li>Every time a "Writer" in the "java.io" package is instantiated from
* within the classes-under-test. You could override the BufferedWriter and/or
* FileWriter with a custom-made mock Writer extending the class. The mock is
* added using the
* {@link net.sf.surrogate.core.SurrogateManager#addMock addMock} method.
* <li>Every time the classes-under-test call
* <code>System.currentTimeMillis()</code>. You could control the time
* returned by using the
* {@link net.sf.surrogate.core.SurrogateManager#addMockMethod addMockMethod}
* </ul>
*
* @author Per S Hustad
*/
public abstract aspect SurrogateCalls {
/**
* Defines the pointcut allowing to substitute method return values with a
* mock object implementation or to substitute a method call or method
* execution with a <code>MockMethod</code>. Resulting implementations of
* this pointcut must be <b>method </b> or </b>constructor </b> pointcuts.
* <p>
* <strong>Note: </strong> If you define a pointcut on a method returning,
* say <code>java.lang.Object</code> be aware of that the return value
* almost certainly will be subsituteted by any mock object, since all
* Objects inherits from <code>java.lang.Object</code>.
*/
protected abstract pointcut mockPointcut();
/*
* Tell the AspectJ weaver to fail with an error message if the subclasses
* implements mockPointcuts which are not "call" or "execution". Otherwise, a
* runtime exception would be thrown by the SurrogateManager
*/
declare error : (mockPointcut() &&
!(call(* *(..)) || execution(* *(..)) ||
call(*.new(..)) || execution(*.new(..))
))
:
"surrogate: \"mockPointcuts\" must be \"call\" or \"execution\" pointcuts";
/**
* Executes when a <code>mockPointcut</code> pointcut is detected. If a
* mock has been registered, that object is allowed to execute, otherwise,
* the original code is allowed to proceed.
* <p>
* <b>Throws </b> Throwable - any Throwable thrown by the SurrogateManager or
* executing mock is catched and rethrown.
* <p>
* <b>See also </b><a
* href="SurrogateManager.html#getMockExecutor">SurrogateManager.getMockExecutor
* </a> <b><a href="ExceptionThrower.html">ExceptionThrower </a> <b>
*/
Object around() : mockPointcut()
{
try
{
SurrogateManager.MockExecutor e =
SurrogateManager.getInstance().getMockExecutor(thisJoinPoint);
return e != null ? e.execute(thisJoinPoint.getArgs()) : proceed();
} catch (Throwable t) {
ExceptionThrower.throwThrowable(t);
return null;
}
}
}
|