From 919fc0f8ff04ef570dd565612e02b875b591deb9 Mon Sep 17 00:00:00 2001 From: dsyer Date: Thu, 7 Jan 2010 07:50:21 +0000 Subject: [PATCH] RESOLVED - issue BATCH-1481: Support injection of step-scoped dependencies into unit tests Revert a few of the more complicated options: just support a factory method --- .../resources/jobs/iosample/delimited.xml | 12 +- .../iosample/AbstractIoSampleTests.java | 4 + .../iosample/DelimitedFunctionalTests.java | 13 +- .../test/StepScopeTestExecutionListener.java | 182 ++++-------------- ...TestExecutionListenerIntegrationTests.java | 17 +- .../StepScopeTestExecutionListenerTests.java | 143 +++----------- 6 files changed, 88 insertions(+), 283 deletions(-) diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml b/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml index 747f348c4..990bb0d6e 100644 --- a/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml +++ b/spring-batch-samples/src/main/resources/jobs/iosample/delimited.xml @@ -29,16 +29,12 @@ - + - - - - - + @@ -50,9 +46,5 @@ - - - - diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/AbstractIoSampleTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/AbstractIoSampleTests.java index 72882c93b..b1ef02e80 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/AbstractIoSampleTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/AbstractIoSampleTests.java @@ -16,8 +16,11 @@ import org.springframework.batch.item.ItemStream; import org.springframework.batch.sample.domain.trade.CustomerCredit; import org.springframework.batch.sample.domain.trade.internal.CustomerCreditIncreaseProcessor; import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.batch.test.StepScopeTestExecutionListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; /** * Base class for IoSample tests that increase input customer credit by fixed @@ -27,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration; * @author Robert Kasanicky */ @ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/job-runner-context.xml", "/jobs/ioSampleJob.xml" }) +@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, StepScopeTestExecutionListener.class}) public abstract class AbstractIoSampleTests { @Autowired diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/DelimitedFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/DelimitedFunctionalTests.java index b5e04188e..460b18894 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/DelimitedFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/DelimitedFunctionalTests.java @@ -16,14 +16,14 @@ package org.springframework.batch.sample.iosample; +import java.util.Collections; +import java.util.Map; + import org.junit.runner.RunWith; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.sample.domain.trade.CustomerCredit; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -35,13 +35,12 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(locations = "/jobs/iosample/delimited.xml") public class DelimitedFunctionalTests extends AbstractIoSampleTests { - @Autowired - private Resource outputResource; + @SuppressWarnings("unused") + private Map jobParameters = Collections.singletonMap("fileName", + "file:./target/test-outputs/delimitedOutput.csv"); @Override protected void pointReaderToOutput(ItemReader reader) { - FlatFileItemReader fileReader = (FlatFileItemReader) reader; - fileReader.setResource(outputResource); } @Override diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java index 798407282..5c143ef3d 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestExecutionListener.java @@ -15,38 +15,24 @@ */ package org.springframework.batch.test; -import java.lang.reflect.Field; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; +import java.lang.reflect.Method; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameter; -import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.scope.context.StepContext; import org.springframework.batch.core.scope.context.StepSynchronizationManager; -import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.adapter.HippyMethodInvoker; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.FieldCallback; +import org.springframework.util.ReflectionUtils.MethodCallback; /** * A {@link TestExecutionListener} that sets up step-scope context for * dependency injection into unit tests. A {@link StepContext} will be created * for the duration of a test method and made available to any dependencies that - * are injected. The default behaviour is just to create a {@link JobExecution} - * and {@link StepExecution} with fixed properties. Alternatively they can be - * provided by the test case as a field of the correct type. If those fields are - * not provided then an {@link ExecutionContext} for the default step execution - * can be specified as a field of type ExecutionContext, or a field of type Map - * (those fields can have any name but to disambiguate you can use the special - * name "executionContext". And finally, {@link JobParameters} can be specified - * using the same convention: a field of that type or a Map (with the field name - * "jobParameters" used to disambiguate). Example: + * are injected. The default behaviour is just to create a {@link StepExecution} + * with fixed properties. Alternatively it can be provided by the test case as a + * factory methods returning the correct type. Example: * *
  * @ContextConfiguration
@@ -58,9 +44,16 @@ import org.springframework.util.ReflectionUtils.FieldCallback;
  * 	@Autowired
  * 	private ItemReader<String> reader;
  * 
+ *  public StepExecution getStepExecution() {
+ *    StepExecution execution = MetaDataInstanceFactory.createStepExecution();
+ *    execution.getExecutionContext().putString("foo", "bar");
+ *    return execution;
+ *  }
+ * 
  * 	@Test
  * 	public void testStepScopedReader() {
- * 		// Step context is active here so the reader can be used...
+ * 		// Step context is active here so the reader can be used,
+ *      // and the step execution context will contain foo=bar...
  * 		assertNotNull(reader.read());
  * 	}
  * 
@@ -123,138 +116,37 @@ public class StepScopeTestExecutionListener implements TestExecutionListener {
 
 		Object target = testContext.getTestInstance();
 
-		ExtractorFieldCallback extractor = new ExtractorFieldCallback(StepExecution.class, "stepExecution");
-		ReflectionUtils.doWithFields(target.getClass(), extractor);
-		if (extractor.getName() != null) {
-			return (StepExecution) ReflectionTestUtils.getField(target, extractor.getName());
-		}
-
-		StepExecution stepExecution = null;
-
-		extractor = new ExtractorFieldCallback(Map.class, "executionContext");
-		ReflectionUtils.doWithFields(target.getClass(), extractor);
-
-		Map map = null;
-
-		if (extractor.getName() == null) {
-			extractor = new ExtractorFieldCallback(ExecutionContext.class, "executionContext");
-			ReflectionUtils.doWithFields(target.getClass(), extractor);
-			if (extractor.getName() != null) {
-				map = new HashMap();
-				ExecutionContext executionContext = ((ExecutionContext) ReflectionTestUtils.getField(target, extractor
-						.getName()));
-				for (Entry entry : executionContext.entrySet()) {
-					map.put(entry.getKey(), entry.getValue());
-				}
+		ExtractorMethodCallback method = new ExtractorMethodCallback(StepExecution.class, "getStepExecution");
+		ReflectionUtils.doWithMethods(target.getClass(), method);
+		if (method.getName() != null) {
+			HippyMethodInvoker invoker = new HippyMethodInvoker();
+			invoker.setTargetObject(target);
+			invoker.setTargetMethod(method.getName());
+			try {
+				invoker.prepare();
+				return (StepExecution) invoker.invoke();
+			}
+			catch (Exception e) {
+				throw new IllegalArgumentException("Could not create step execution from method: " + method.getName(),
+						e);
 			}
 		}
-		else {
-			@SuppressWarnings("unchecked")
-			Map themap = (Map) ReflectionTestUtils
-					.getField(target, extractor.getName());
-			map = themap;
-		}
-
-		JobExecution jobExecution = getJobExecution(testContext);
-		if (map == null) {
-			map = new HashMap();
-		}
-		if (stepExecution == null) {
-			if (jobExecution != null) {
-				stepExecution = jobExecution.createStepExecution("step");
-			}
-			else {
-				stepExecution = MetaDataInstanceFactory.createStepExecution();
-			}
-		}
-		for (String key : map.keySet()) {
-			stepExecution.getExecutionContext().put(key, map.get(key));
-		}
-
-		return stepExecution;
 
+		return MetaDataInstanceFactory.createStepExecution();
 	}
 
 	/**
-	 * Discover a {@link JobExecution} as a field in the test case or create
-	 * one if none is available.
-	 * 
-	 * @param testContext the current test context
-	 * @return a {@link JobExecution}
+	 * Look for a method returning the type provided, preferring one with the
+	 * name provided.
 	 */
-	private JobExecution getJobExecution(TestContext testContext) {
-
-		Object target = testContext.getTestInstance();
-
-		ExtractorFieldCallback extractor = new ExtractorFieldCallback(JobExecution.class, "jobExecution");
-		ReflectionUtils.doWithFields(target.getClass(), extractor);
-		if (extractor.getName() != null) {
-			return (JobExecution) ReflectionTestUtils.getField(target, extractor.getName());
-		}
-
-		extractor = new ExtractorFieldCallback(Map.class, "jobParameters");
-		ReflectionUtils.doWithFields(target.getClass(), extractor);
-
-		Map map = null;
-
-		if (extractor.getName() == null) {
-			extractor = new ExtractorFieldCallback(JobParameters.class, "jobParameters");
-			ReflectionUtils.doWithFields(target.getClass(), extractor);
-			if (extractor.getName() != null) {
-				map = new HashMap();
-				JobParameters jobParameters = ((JobParameters) ReflectionTestUtils
-						.getField(target, extractor.getName()));
-				for (Entry entry : jobParameters.getParameters().entrySet()) {
-					map.put(entry.getKey(), entry.getValue().getValue());
-				}
-			}
-		}
-		else {
-			@SuppressWarnings("unchecked")
-			Map themap = (Map) ReflectionTestUtils
-					.getField(target, extractor.getName());
-			map = themap;
-		}
-
-		if (map != null) {
-			Map parameters = new HashMap();
-			for (String key : map.keySet()) {
-				Object value = map.get(key);
-				if (value == null) {
-					parameters.put(key, new JobParameter((String) null));
-				}
-				else if (value instanceof String) {
-					parameters.put(key, new JobParameter((String) value));
-				}
-				else if (value instanceof Double) {
-					parameters.put(key, new JobParameter((Double) value));
-				}
-				else if (value instanceof Long) {
-					parameters.put(key, new JobParameter((Long) value));
-				}
-				else if (value instanceof Date) {
-					parameters.put(key, new JobParameter((Date) value));
-				}
-			}
-			return MetaDataInstanceFactory.createJobExecution("job", 11L, 123L, new JobParameters(parameters));
-		}
-
-		return null;
-
-	}
-
-	/**
-	 * Look for a Map in the fields provided, preferring one with the name
-	 * provided.
-	 */
-	private final class ExtractorFieldCallback implements FieldCallback {
+	private final class ExtractorMethodCallback implements MethodCallback {
 		private String preferredName;
 
 		private final Class preferredType;
 
-		private Field result;
+		private Method result;
 
-		public ExtractorFieldCallback(Class preferredType, String preferredName) {
+		public ExtractorMethodCallback(Class preferredType, String preferredName) {
 			super();
 			this.preferredType = preferredType;
 			this.preferredName = preferredName;
@@ -264,11 +156,11 @@ public class StepScopeTestExecutionListener implements TestExecutionListener {
 			return result == null ? null : result.getName();
 		}
 
-		public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
-			Class type = field.getType();
+		public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+			Class type = method.getReturnType();
 			if (preferredType.isAssignableFrom(type)) {
-				if (result == null || field.getName().equals(preferredName)) {
-					result = field;
+				if (result == null || method.getName().equals(preferredName)) {
+					result = method;
 				}
 			}
 		}
diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java
index 001f8e227..863158f4c 100644
--- a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java
+++ b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerIntegrationTests.java
@@ -1,12 +1,11 @@
 package org.springframework.batch.test;
 
 import static org.junit.Assert.assertEquals;
-
-import java.util.Collections;
-import java.util.Map;
+import static org.junit.Assert.assertNotNull;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.springframework.batch.core.StepExecution;
 import org.springframework.batch.item.ExecutionContext;
 import org.springframework.batch.item.ItemReader;
 import org.springframework.batch.item.ItemStream;
@@ -31,11 +30,13 @@ public class StepScopeTestExecutionListenerIntegrationTests {
 	@Autowired
 	private ItemStream stream;
 
-	protected Map executionContext;
-
-	public StepScopeTestExecutionListenerIntegrationTests() {
-		executionContext = Collections.singletonMap("input.file",
-				(Object) "classpath:/org/springframework/batch/test/simple.txt");
+	public StepExecution getStepExection() {
+		// Assert that dependencies are already injected...
+		assertNotNull(reader);
+		// Then create the execution for the step scope...
+		StepExecution execution = MetaDataInstanceFactory.createStepExecution();
+		execution.getExecutionContext().putString("input.file", "classpath:/org/springframework/batch/test/simple.txt");
+		return execution;
 	}
 
 	@Test
diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java
index 4d89e04b1..cae61a8fb 100644
--- a/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java
+++ b/spring-batch-test/src/test/java/org/springframework/batch/test/StepScopeTestExecutionListenerTests.java
@@ -4,17 +4,12 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import java.util.Collections;
-import java.util.Map;
-
 import org.junit.Test;
 import org.springframework.batch.core.JobExecution;
-import org.springframework.batch.core.JobParameters;
 import org.springframework.batch.core.JobParametersBuilder;
 import org.springframework.batch.core.StepExecution;
 import org.springframework.batch.core.scope.context.StepContext;
 import org.springframework.batch.core.scope.context.StepSynchronizationManager;
-import org.springframework.batch.item.ExecutionContext;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.TestContext;
 import org.springframework.test.context.TestContextManager;
@@ -29,7 +24,7 @@ public class StepScopeTestExecutionListenerTests {
 	private StepScopeTestExecutionListener listener = new StepScopeTestExecutionListener();
 
 	@Test
-	public void testStepContext() throws Exception {
+	public void testDefaultStepContext() throws Exception {
 		TestContext testContext = getTestContext(new Object());
 		listener.prepareTestInstance(testContext);
 		listener.beforeTestMethod(testContext);
@@ -40,134 +35,56 @@ public class StepScopeTestExecutionListenerTests {
 	}
 
 	@Test
-	public void testWithStepExecution() throws Exception {
-		testExecutionContext(new WithStepExecution());
-	}
-
-	@Test
-	public void testWithMapForContext() throws Exception {
-		testExecutionContext(new WithMap());
-	}
-
-	@Test
-	public void testWithTwoMapForContext() throws Exception {
-		testExecutionContext(new WithTwoMaps());
-	}
-
-	@Test
-	public void testWithContextForContext() throws Exception {
-		testExecutionContext(new WithExecutionContext());
-	}
-
-	@Test
-	public void testWithTwpContextsForContext() throws Exception {
-		testExecutionContext(new WithTwoMaps());
-	}
-
-	@Test
-	public void testWithJobExecution() throws Exception {
-		testJobParameters(new WithJobExecution());
-	}
-
-	@Test
-	public void testWithMapForParameters() throws Exception {
-		testJobParameters(new WithMap());
-	}
-
-	@Test
-	public void testWithTwoMapsForParameters() throws Exception {
-		testJobParameters(new WithParametersMap());
+	public void testWithStepExecutionFactory() throws Exception {
+		testExecutionContext(new WithStepExecutionFactory());
 	}
 
 	@Test
 	public void testWithParameters() throws Exception {
-		testJobParameters(new WithParameters());
-	}
-
-	@Test
-	public void testWithTwoParameters() throws Exception {
-		testJobParameters(new WithTwoParameters());
+		testJobParameters(new WithStepExecutionFactory());
 	}
 
 	private void testExecutionContext(Object target) throws Exception {
 		TestContext testContext = getTestContext(target);
 		listener.prepareTestInstance(testContext);
-		listener.beforeTestMethod(testContext);
-		StepContext context = StepSynchronizationManager.getContext();
-		assertNotNull(context);
-		assertEquals("bar", context.getStepExecutionContext().get("foo"));
-		listener.afterTestMethod(testContext);
+		try {
+			listener.beforeTestMethod(testContext);
+			StepContext context = StepSynchronizationManager.getContext();
+			assertNotNull(context);
+			assertEquals("bar", context.getStepExecutionContext().get("foo"));
+		}
+		finally {
+			listener.afterTestMethod(testContext);
+		}
 		assertNull(StepSynchronizationManager.getContext());
 	}
 
 	private void testJobParameters(Object target) throws Exception {
 		TestContext testContext = getTestContext(target);
 		listener.prepareTestInstance(testContext);
-		listener.beforeTestMethod(testContext);
-		StepContext context = StepSynchronizationManager.getContext();
-		assertNotNull(context);
-		assertEquals("bar", context.getJobParameters().get("foo"));
-		listener.afterTestMethod(testContext);
+		try {
+			listener.beforeTestMethod(testContext);
+			StepContext context = StepSynchronizationManager.getContext();
+			assertNotNull(context);
+			assertEquals("spam", context.getJobParameters().get("foo"));
+		}
+		finally {
+			listener.afterTestMethod(testContext);
+		}
 		assertNull(StepSynchronizationManager.getContext());
 	}
 
-	private static class WithStepExecution {
-		private StepExecution execution = MetaDataInstanceFactory.createStepExecution();
-
-		public WithStepExecution() {
-			execution.getExecutionContext().putString("foo", "bar");
+	@SuppressWarnings("unused")
+	private static class WithStepExecutionFactory {
+		public StepExecution getStepExecution() {
+			JobExecution jobExecution = MetaDataInstanceFactory.createJobExecution("job", 11L, 123L,
+					new JobParametersBuilder().addString("foo", "spam").toJobParameters());
+			StepExecution stepExecution = jobExecution.createStepExecution("step");
+			stepExecution.getExecutionContext().putString("foo", "bar");
+			return stepExecution;
 		}
 	}
 
-	@SuppressWarnings("unused")
-	private static class WithJobExecution {
-		private JobExecution execution = MetaDataInstanceFactory.createJobExecution("job", 11L, 123L, new JobParametersBuilder().addString("foo", "bar").toJobParameters());
-	}
-
-	@SuppressWarnings("unused")
-	private static class WithMap {
-		private Map context = Collections.singletonMap("foo", (Object) "bar");
-	}
-
-	@SuppressWarnings("unused")
-	private static class WithTwoMaps {
-		private Map executionContext = Collections.singletonMap("foo", (Object) "bar");
-
-		private Map map = Collections.singletonMap("foo", (Object) "spam");
-	}
-
-	@SuppressWarnings("unused")
-	private static class WithParametersMap {
-		private Map jobParameters = Collections.singletonMap("foo", (Object) "bar");
-
-		private Map map = Collections.singletonMap("foo", (Object) "spam");
-	}
-
-	@SuppressWarnings("unused")
-	private static class WithExecutionContext {
-		private ExecutionContext context = new ExecutionContext(Collections.singletonMap("foo", (Object) "bar"));
-	}
-
-	@SuppressWarnings("unused")
-	public static class WithTwoExecutionContextw {
-		private ExecutionContext executionContext = new ExecutionContext(Collections
-				.singletonMap("foo", (Object) "bar"));
-
-		private ExecutionContext context = new ExecutionContext(Collections.singletonMap("foo", (Object) "spam"));
-	}
-
-	@SuppressWarnings("unused")
-	private static class WithParameters {
-		private JobParameters params = new JobParametersBuilder().addString("foo", "bar").toJobParameters();
-	}
-
-	@SuppressWarnings("unused")
-	private static class WithTwoParameters {
-		private JobParameters jobParemeters = new JobParametersBuilder().addString("foo", "bar").toJobParameters();
-
-		private JobParameters params = new JobParametersBuilder().addString("foo", "spam").toJobParameters();
-	}
-
 	private TestContext getTestContext(Object target) throws Exception {
 		return new MockTestContextManager(target, getClass()).getContext();
 	}