Posted By

roock on 02/25/08


Tagged

testing mock


Versions (?)

Half-Mock


 / Published in: Groovy
 

http://jira.codehaus.org/browse/GROOVY-2630

  1. package halfmock
  2.  
  3. import groovy.lang.*;
  4.  
  5. import java.beans.IntrospectionException;
  6.  
  7. /**
  8.  * The ProxyMetaClass for the MockInterceptor.
  9.  * Instance and class methods are intercepted, but constructors are not to allow mocking of aggregated objects.
  10.  * @author Stefan Roock (based on MockProxyMetaClass by Dierk Koenig)
  11.  */
  12.  
  13. public class HalfMockProxyMetaClass extends ProxyMetaClass {
  14.  
  15. def methodNames = []
  16. def propertyNames = []
  17. def registeredInterceptor
  18.  
  19. /**
  20.   * @param adaptee the MetaClass to decorate with interceptability
  21.   */
  22. public HalfMockProxyMetaClass(MetaClassRegistry registry, Class theClass, MetaClass adaptee) throws IntrospectionException {
  23. super(registry, theClass, adaptee);
  24. methodNames = theClass.declaredMethods.name.toList()
  25. def clazz = theClass
  26. while (clazz != null) {
  27. propertyNames.addAll( clazz.declaredFields.name.toList() )
  28. clazz = clazz.superclass
  29. }
  30. }
  31.  
  32. /**
  33.   * convenience factory method for the most usual case.
  34.   */
  35. public static HalfMockProxyMetaClass make(Class theClass) throws IntrospectionException {
  36. MetaClassRegistry metaRegistry = GroovySystem.getMetaClassRegistry();
  37. MetaClass meta = metaRegistry.getMetaClass(theClass);
  38. return new HalfMockProxyMetaClass(metaRegistry, theClass, meta);
  39. }
  40.  
  41.  
  42. public Object invokeMethod(final Object object, final String methodName, final Object[] arguments) {
  43. if (!restoreInterceptor()) {
  44. throw new RuntimeException("cannot invoke method ${methodName} without interceptor");
  45. }
  46. if (!methodNames.contains(methodName)) {
  47. return interceptor.beforeInvoke(object, methodName, arguments);
  48. }
  49. return doWithoutInterceptor {
  50. super.invokeMethod(object, methodName, arguments)
  51. }
  52. }
  53.  
  54. public Object invokeStaticMethod(final Object object, final String methodName, final Object[] arguments) {
  55. if (!restoreInterceptor()) {
  56. throw new RuntimeException("cannot invoke static method ${methodName} without interceptor");
  57. }
  58. if (!methodNames.contains(methodName)) {
  59. return interceptor.beforeInvoke(object, methodName, arguments);
  60. }
  61. return doWithoutInterceptor {
  62. super.invokeStaticMethod(object, methodName, arguments)
  63. }
  64. }
  65.  
  66. public Object getProperty(Class aClass, Object object, String property, boolean b, boolean b1) {
  67. if (!restoreInterceptor()) {
  68. throw new RuntimeException("cannot get property ${property} without interceptor");
  69. }
  70. if (interceptor instanceof PropertyAccessInterceptor && !propertyNames.contains(property)) {
  71. return ((PropertyAccessInterceptor) interceptor).beforeGet(object, property);
  72. }
  73. else {
  74. return doWithoutInterceptor {
  75. super.getProperty(aClass, object, property, b, b)
  76. }
  77. }
  78.  
  79. }
  80.  
  81. public void setProperty(Class aClass, Object object, String property, Object newValue, boolean b, boolean b1) {
  82. if (!restoreInterceptor()) {
  83. throw new RuntimeException("cannot set property ${property} without interceptor");
  84. }
  85.  
  86. if (interceptor instanceof PropertyAccessInterceptor && !propertyNames.contains(property)) {
  87. ((PropertyAccessInterceptor) interceptor).beforeSet(object, property, newValue);
  88. }
  89. else {
  90. doWithoutInterceptor {
  91. super.setProperty(aClass, object, property, newValue, b, b)
  92. }
  93. }
  94.  
  95. }
  96.  
  97. /**
  98.   * Unlike general impl in superclass, ctors are not intercepted but relayed
  99.   */
  100. public Object invokeConstructor(final Object[] arguments) {
  101. return adaptee.invokeConstructor(arguments);
  102. }
  103.  
  104. private doWithoutInterceptor(closure) {
  105. registeredInterceptor = interceptor
  106. interceptor = null
  107. def result = closure.call()
  108. restoreInterceptor()
  109. result
  110. }
  111.  
  112. private restoreInterceptor() {
  113. interceptor = interceptor ?: registeredInterceptor
  114. return interceptor
  115. }
  116.  
  117. }
  118.  
  119.  
  120.  
  121.  
  122. package halfmock
  123.  
  124. import groovy.mock.interceptor.*
  125.  
  126. /**
  127.   Facade over the Mocking details.
  128.   A Mock's expectation is always sequence dependent and it's use always ends with a verify().
  129.   See also StubFor.
  130.   @author Stefan Roock (based on MockFor from Dierk Koenig and Paul King
  131. */
  132. class HalfMockFor {
  133.  
  134. HalfMockProxyMetaClass proxy
  135. Demand demand
  136. def expect
  137. Map instanceExpectations = [:]
  138. Class clazz
  139.  
  140. HalfMockFor(Class clazz) {
  141. this.clazz = clazz
  142. proxy = HalfMockProxyMetaClass.make(clazz)
  143. demand = new Demand()
  144. expect = new StrictExpectation(demand)
  145. proxy.interceptor = new MockInterceptor(expectation: expect)
  146. }
  147.  
  148. void use(Closure closure) {
  149. proxy.use closure
  150. expect.verify()
  151. }
  152.  
  153. void use(GroovyObject obj, Closure closure) {
  154. proxy.use obj, closure
  155. expect.verify()
  156. }
  157.  
  158. void verify(GroovyObject obj) {
  159. instanceExpectations[obj].verify()
  160. }
  161.  
  162. Object proxyInstance() {
  163. proxyInstance(null)
  164. }
  165.  
  166. Object proxyInstance(args) {
  167. def instance = getInstance(clazz, args)
  168. def thisproxy = HalfMockProxyMetaClass.make(clazz)
  169. def thisdemand = new Demand(recorded: new ArrayList(demand.recorded))
  170. def thisexpect = new StrictExpectation(thisdemand)
  171. thisproxy.interceptor = new MockInterceptor(expectation: thisexpect)
  172. instance.metaClass = thisproxy
  173. instanceExpectations[instance] = thisexpect
  174. return instance
  175. }
  176.  
  177. Object proxyDelegateInstance() {
  178. proxyDelegateInstance(null)
  179. }
  180.  
  181. Object proxyDelegateInstance(args) {
  182. def instance = getInstance(clazz, args)
  183. def thisproxy = HalfMockProxyMetaClass.make(clazz)
  184. def thisdemand = new Demand(recorded: new ArrayList(demand.recorded))
  185. def thisexpect = new StrictExpectation(thisdemand)
  186. thisproxy.interceptor = new MockInterceptor(expectation: thisexpect)
  187. instance.metaClass = thisproxy
  188. def wrapped = null
  189. if (clazz.isInterface()) {
  190. wrapped = ProxyGenerator.instantiateDelegate([clazz], instance)
  191. } else {
  192. wrapped = ProxyGenerator.instantiateDelegate(instance)
  193. }
  194. instanceExpectations[wrapped] = thisexpect
  195. return wrapped
  196. }
  197.  
  198. private getInstance(Class clazz, args) {
  199. def instance = null
  200. if (clazz.isInterface()) {
  201. instance = ProxyGenerator.instantiateAggregateFromInterface(clazz)
  202. } else if (Modifier.isAbstract(clazz.modifiers)) {
  203. instance = ProxyGenerator.instantiateAggregateFromBaseClass(clazz, args)
  204. } else if (args != null) {
  205. if (clazz instanceof GroovyObject) {
  206. instance = clazz.newInstance(args)
  207. } else {
  208. instance = ProxyGenerator.instantiateDelegate(clazz.newInstance(args))
  209. }
  210. } else {
  211. if (clazz instanceof GroovyObject) {
  212. instance = clazz.newInstance()
  213. } else {
  214. instance = ProxyGenerator.instantiateDelegate(clazz.newInstance())
  215. }
  216. }
  217. return instance
  218. }
  219.  
  220. }
  221.  
  222.  
  223.  
  224.  
  225. // Test
  226. package halfmock
  227.  
  228. import groovy.mock.interceptor.*
  229.  
  230. class Foo {
  231.  
  232. static existsCalls = 0, staticExistsCalls = 0, constructorCalls = 0
  233. def existingProperty = 0
  234.  
  235. Foo() {
  236. constructorCalls++
  237. }
  238.  
  239. void exists() {
  240. existsCalls++
  241. }
  242.  
  243. void callsDoesntExist() {
  244. doesntExist()
  245. }
  246.  
  247. static void staticExists() {
  248. staticExistsCalls++
  249. }
  250. }
  251.  
  252. class Bar extends Foo {
  253.  
  254. }
  255.  
  256. class HalfMockTest extends GroovyTestCase {
  257.  
  258. void setUp() {
  259. Foo.constructorCalls = 0
  260. Foo.staticExistsCalls = 0
  261. Foo.existsCalls = 0
  262. }
  263.  
  264. void testCallsConstructorOfMockedObject() {
  265. def mock = new HalfMockFor(Foo.class)
  266. mock.use {
  267. def foo = new Foo()
  268. }
  269. assertEquals 1, Foo.constructorCalls
  270. }
  271.  
  272. void testMocksNonExistingMethods() {
  273. def mock = new HalfMockFor(Foo.class)
  274. mock.demand.doesntExist() {}
  275. mock.use {
  276. def foo = new Foo()
  277. foo.doesntExist()
  278. }
  279. }
  280.  
  281. void testDoesntMockExistingMethods() {
  282. def mock = new HalfMockFor(Foo.class)
  283. mock.use {
  284. def foo = new Foo()
  285. foo.exists()
  286. }
  287. assertEquals 1, Foo.existsCalls
  288. }
  289.  
  290.  
  291. void testMocksIndirectlyCalledNonExistingMethods() {
  292. def mock = new HalfMockFor(Foo.class)
  293. mock.demand.doesntExist() {}
  294. mock.use {
  295. def foo = new Foo()
  296. foo.callsDoesntExist()
  297. }
  298. }
  299.  
  300.  
  301. void testMocksNonExistingStaticMethods() {
  302. def mock = new HalfMockFor(Foo.class)
  303. mock.demand.staticDoesntExist() {}
  304. mock.use {
  305. def foo = new Foo()
  306. Foo.staticDoesntExist()
  307. }
  308. }
  309.  
  310. void testDoesntMockExistingStaticMethods() {
  311. def mock = new HalfMockFor(Foo.class)
  312. mock.use {
  313. def foo = new Foo()
  314. Foo.staticExists()
  315. }
  316. assertEquals 1, Foo.staticExistsCalls
  317. }
  318.  
  319. void testMocksNonExistingProperties() {
  320. def mock = new HalfMockFor(Foo.class)
  321. mock.demand.setNonExistingProperty(1) {}
  322. mock.demand.getNonExistingProperty() {2}
  323. mock.use {
  324. def foo = new Foo()
  325. foo.nonExistingProperty = 1
  326. assertEquals 2, foo.nonExistingProperty
  327. }
  328. }
  329.  
  330. void testDoesntMockExistingProperties() {
  331. def mock = new HalfMockFor(Foo.class)
  332. mock.use {
  333. def foo = new Foo()
  334. foo.existingProperty = 1
  335. assertEquals 1, foo.existingProperty
  336. }
  337. }
  338.  
  339. void testDoesntMockExistingInheritedProperties() {
  340. def mock = new HalfMockFor(Bar.class)
  341. mock.use {
  342. def bar = new Bar()
  343. bar.existingProperty = 1
  344. assertEquals 1, bar.existingProperty
  345. }
  346. }
  347.  
  348. }

Report this snippet  

You need to login to post a comment.