View Javadoc

1   /*
2    * Copyright 2006-2010 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.springframework.batch.test;
17  
18  import java.lang.reflect.Method;
19  
20  import org.springframework.batch.core.StepExecution;
21  import org.springframework.batch.core.scope.context.StepContext;
22  import org.springframework.batch.core.scope.context.StepSynchronizationManager;
23  import org.springframework.batch.item.adapter.HippyMethodInvoker;
24  import org.springframework.test.context.TestContext;
25  import org.springframework.test.context.TestExecutionListener;
26  import org.springframework.util.ReflectionUtils;
27  import org.springframework.util.ReflectionUtils.MethodCallback;
28  
29  /**
30   * A {@link TestExecutionListener} that sets up step-scope context for
31   * dependency injection into unit tests. A {@link StepContext} will be created
32   * for the duration of a test method and made available to any dependencies that
33   * are injected. The default behaviour is just to create a {@link StepExecution}
34   * with fixed properties. Alternatively it can be provided by the test case as a
35   * factory methods returning the correct type.  Example:
36   * 
37   * <pre>
38   * &#064;ContextConfiguration
39   * &#064;TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class })
40   * &#064;RunWith(SpringJUnit4ClassRunner.class)
41   * public class StepScopeTestExecutionListenerIntegrationTests {
42   * 
43   * 	// A step-scoped dependency configured in the ApplicationContext
44   * 	&#064;Autowired
45   * 	private ItemReader&lt;String&gt; reader;
46   * 
47   *  public StepExecution getStepExecution() {
48   *    StepExecution execution = MetaDataInstanceFactory.createStepExecution();
49   *    execution.getExecutionContext().putString("foo", "bar");
50   *    return execution;
51   *  }
52   * 
53   * 	&#064;Test
54   * 	public void testStepScopedReader() {
55   * 		// Step context is active here so the reader can be used,
56   *      // and the step execution context will contain foo=bar...
57   * 		assertNotNull(reader.read());
58   * 	}
59   * 
60   * }
61   * </pre>
62   * 
63   * @author Dave Syer
64   * 
65   */
66  public class StepScopeTestExecutionListener implements TestExecutionListener {
67  
68  	private static final String STEP_EXECUTION = StepScopeTestExecutionListener.class.getName() + ".STEP_EXECUTION";
69  
70  	/**
71  	 * Set up a {@link StepExecution} as a test context attribute.
72  	 * 
73  	 * @param testContext the current test context
74  	 * @throws Exception if there is a problem
75  	 * @see TestExecutionListener#prepareTestInstance(TestContext)
76  	 */
77      @Override
78  	public void prepareTestInstance(TestContext testContext) throws Exception {
79  		StepExecution stepExecution = getStepExecution(testContext);
80  		if (stepExecution != null) {
81  			testContext.setAttribute(STEP_EXECUTION, stepExecution);
82  		}
83  	}
84  
85  	/**
86  	 * @param testContext the current test context
87  	 * @throws Exception if there is a problem
88  	 * @see TestExecutionListener#beforeTestMethod(TestContext)
89  	 */
90      @Override
91  	public void beforeTestMethod(org.springframework.test.context.TestContext testContext) throws Exception {
92  		if (testContext.hasAttribute(STEP_EXECUTION)) {
93  			StepExecution stepExecution = (StepExecution) testContext.getAttribute(STEP_EXECUTION);
94  			StepSynchronizationManager.register(stepExecution);
95  		}
96  
97  	}
98  
99  	/**
100 	 * @param testContext the current test context
101 	 * @throws Exception if there is a problem
102 	 * @see TestExecutionListener#afterTestMethod(TestContext)
103 	 */
104     @Override
105 	public void afterTestMethod(TestContext testContext) throws Exception {
106 		if (testContext.hasAttribute(STEP_EXECUTION)) {
107 			StepSynchronizationManager.close();
108 		}
109 	}
110 
111 	/*
112 	 * Support for Spring 3.0 (empty).
113 	 */
114     @Override
115 	public void afterTestClass(TestContext testContext) throws Exception {
116 	}
117 
118 	/*
119 	 * Support for Spring 3.0 (empty).
120 	 */
121     @Override
122 	public void beforeTestClass(TestContext testContext) throws Exception {
123 	}
124 	
125 	/**
126 	 * Discover a {@link StepExecution} as a field in the test case or create
127 	 * one if none is available.
128 	 * 
129 	 * @param testContext the current test context
130 	 * @return a {@link StepExecution}
131 	 */
132 	protected StepExecution getStepExecution(TestContext testContext) {
133 
134 		Object target = testContext.getTestInstance();
135 
136 		ExtractorMethodCallback method = new ExtractorMethodCallback(StepExecution.class, "getStepExecution");
137 		ReflectionUtils.doWithMethods(target.getClass(), method);
138 		if (method.getName() != null) {
139 			HippyMethodInvoker invoker = new HippyMethodInvoker();
140 			invoker.setTargetObject(target);
141 			invoker.setTargetMethod(method.getName());
142 			try {
143 				invoker.prepare();
144 				return (StepExecution) invoker.invoke();
145 			}
146 			catch (Exception e) {
147 				throw new IllegalArgumentException("Could not create step execution from method: " + method.getName(),
148 						e);
149 			}
150 		}
151 
152 		return MetaDataInstanceFactory.createStepExecution();
153 	}
154 
155 	/**
156 	 * Look for a method returning the type provided, preferring one with the
157 	 * name provided.
158 	 */
159 	private final class ExtractorMethodCallback implements MethodCallback {
160 		private String preferredName;
161 
162 		private final Class<?> preferredType;
163 
164 		private Method result;
165 
166 		public ExtractorMethodCallback(Class<?> preferredType, String preferredName) {
167 			super();
168 			this.preferredType = preferredType;
169 			this.preferredName = preferredName;
170 		}
171 
172 		public String getName() {
173 			return result == null ? null : result.getName();
174 		}
175 
176         @Override
177 		public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
178 			Class<?> type = method.getReturnType();
179 			if (preferredType.isAssignableFrom(type)) {
180 				if (result == null || method.getName().equals(preferredName)) {
181 					result = method;
182 				}
183 			}
184 		}
185 	}
186 
187 }