Blog

Unit-Tests im Spring Context mit JUnit und Mockito

Im täglichen Projektgeschäft kommt es noch immer vor, dass Entwickler und auch Projektleiter vom Nutzen von Tests überzeugt werden müssen. Um in einem Umfeld, in dem bis auf Tests durch die Qualitätssicherung noch keine oder nur Alibi-Tests vorliegen, ist es ratsam, nicht gleich den Big Bang zu starten und TDD (Test Driven Development) einführen zu wollen. Ratsam ist eine Orientierung an der klassischen Testpyramide, bei welcher Unit-Tests die Voraussetzung für weitere Tests darstellen.

Sind die Entwickler und Projektleiter einmal ins Boot geholt, muss eine zweite Hürde überwunden werden. Es muss den Entwicklern eine konfigurierte Umgebung zur Verfügung gestellt werden, in welcher diese ihre Tests ohne großen Aufwand schreiben und ausführen können. Welche Umgebung vonnöten ist, um im Spring 2.5 Context mit JUnit 4.4 und dem Mock-Framwork Mockito in Version 1.8.5 testen zu können, soll dieser Blogbeitrag klären.

Einer der großen Vorteile des Einsatzes von Spring ist die Dependency Injection (DI), welche ja jetzt auch aktuell für Java Webanwendungen mit CDI und der entsprechenden Referenzimplementierung Weld von JBoss Einzug in die Java 6 Spezifikation erhält.

Es müssen also in einem Unit-Test die von einem zu testenden Service genutzten Klassen im Context vorhanden sein, um diese auch im Test nutzen bzw. injizieren zu können. Um einen entsprechenden Context im Test zur Verfügung zu stellen, bietet das Test-Package von Spring die Annotation @org.springframework.test.context.ContextConfiguration, welche erlaubt, einen Context zu laden. Um wiederum JUnit mitzuteilen, dass mit Spring getestet wird, muss ein entsprechender Classrunner  mit der Annotation @org.junit.runner.RunWith definiert werden, welcher diesen Context für den Test bereitstellt.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { '/application-config.xml' })
public class FrontendServiceTest {
	// Zu testender FrontendService
	@Resource
	private FrontendService frontendService;
	…
}

Dank dieser zwei Annotationen können die Unit-Tests jetzt im Spring Context ausgeführt, der zu testende FrontendService in den Test injiziert und die im FrontendService genutzten Services in diesen injiziert werden.

@Service
public class FrontendService {

	// injizierter entferner BackendService
	@Resource
	protected BackendService backendService;

	public String getBackendName() {
		return backendService.getName();
	}
}

In der aktuellen Konfiguration wird jedoch auf die „echten“ in der application-config.xml konfigurierten Services zugegriffen. Es würde sich folglich schon eher um einen Integrationstest handeln. Hier soll jedoch ein Unit-Test vollkommen unabhängig von Backend-Daten durchgeführt werden. Dazu soll anstatt mit den „echten“ Services mit Mock-Objekten gearbeitet werden.

Um im Spring Context Mock-Objekte definieren zu können, die dann anstatt der „echten“ Services injiziert werden, wird das Spring-Interface der Factory Bean genutzt und mit einer MockFactory implementiert.

public class MockFactory implements FactoryBean {

	// Typ des erstellten Objektes
	private Class type;

	public void setType(final Class type) {
		this.type = type;
	}

	@Override
	public Object getObject() throws Exception {
		// wird das Objekt angefordert, wird ein entsprechendes Mock-Objekt
		// erzeugt und zurückgegeben
		return mock(type);
	}

	@Override
	public Class getObjectType() {
		return type;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}
}

Anstatt der application-config.xml wird im Test eine für den Test angepasste test-application-config.xml geladen. Anstatt der Konfiguration für den „echten“ Service wird der Service über die MockFactory als Mock-Objekt konfiguriert.

<!-- Konfigurieren des BackendServices als Mock -->
<bean id='contractServiceDelegate'
class='de.holisticon.blog.springContextTest.MockFactory'>
			<property name='type' value='de.holisticon.blog.enterprise.springContextTest.BackendService' >
</bean>

Der eigentliche Test ist jetzt ganz einfach. Ratsam ist, das Verhalten der verwendeten Mockobjekte in einer mit @org.junit.Before annotierten Methode zu definieren und die Mockobjekte nach dem Test innerhalb einer mit @org.junit.After annotierten Methode aufzuräumen, damit keine Wechselwirkungen beim Ausführen mehrerer Tests innerhalb eines Contexts entstehen.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { '/test-application-config.xml' })
public class FrontendServiceTest {

	// Zu testender FrontendService
	@Resource
	private FrontendService frontendService;

	private final String BE_NAME = 'test';

	/**
	 * Bevor der Test ausgeführt wird, wird das gewünschte Verhalten des Mocks
	 * definiert.
	 *
	 * @throws Exception
	 */
	@Before
	public void init() throws Exception {
		when(frontendService.backendService.getName()).thenReturn(BE_NAME);
	}

	/**
	 * Test der Methode getBackendName() des FrontendServices.
	 *
	 * @throws Exception
	 */
	@Test
	public void getBackendNameTest() throws Exception {
		String resultString = frontendService.getBackendName();
		assertNotNull(resultString);
		assertEquals(BE_NAME, resultString);
	}

	/**
	 * Nach Ausführung des Test wird der Mock für den BackendService
	 * zurückgesetzt, damit keine Wechselwirkungen mit anderen Tests entstehen
	 * können.
	 *
	 * @throws Exception
	 */
	@After
	public void clean() throws Exception {
		reset(frontendService.backendService);
	}
}

Der Quellode ist in einer kleinen Beispielapplikation zu finden: SpringContextTest

Holisticon AG — Teile diesen Artikel

Über den Autor

Mr Norman Erck M.Sc. started developing websites as a teen in 1999 driven by his fascination for the possibilities of the rising e-business technologies. He is now a certified ScrumMaster and Enterprise- Software-Architect who has worked on e-business projects for over seven years. He takes the role of a scrum master and architect for IT projects in large companies. He is a speaker on conferences about CDI as well.

Ein Kommentar

Antwort hinterlassen