diff --git a/src/test/java/teetime/stage/CipherByteArrayTest.java b/src/test/java/teetime/stage/CipherByteArrayTest.java
index 8f8d7882cfb32615d84ccb8cb47b95b3d1dc4b25..14f8ce976d31c3ae37182e397b42e1160d7de77a 100644
--- a/src/test/java/teetime/stage/CipherByteArrayTest.java
+++ b/src/test/java/teetime/stage/CipherByteArrayTest.java
@@ -17,17 +17,13 @@ package teetime.stage;
 
 import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
 import static org.junit.Assert.assertThat;
+import static teetime.framework.test.StageTester.test;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import org.junit.Test;
 
-import teetime.framework.Analysis;
-import teetime.framework.AnalysisConfiguration;
-import teetime.framework.pipe.IPipeFactory;
-import teetime.framework.pipe.PipeFactoryRegistry.PipeOrdering;
-import teetime.framework.pipe.PipeFactoryRegistry.ThreadCommunication;
 import teetime.stage.CipherByteArray.CipherMode;
 
 /**
@@ -37,33 +33,17 @@ public class CipherByteArrayTest {
 
 	@Test
 	public void decryptShouldInvertEncryption() {
-		final byte[] input = new byte[] { 1, 2, 3, 4, 5 };
-		final List<byte[]> output = new ArrayList<byte[]>();
-
-		final Configuration configuration = new Configuration(input, output);
-		final Analysis analysis = new Analysis(configuration);
-		analysis.start();
-
-		assertThat(output, contains(input));
-	}
+		final CipherByteArray encryptStage = new CipherByteArray("somePassword", CipherMode.ENCRYPT);
+		final CipherByteArray decryptStage = new CipherByteArray("somePassword", CipherMode.DECRYPT);
 
-	private class Configuration extends AnalysisConfiguration {
-
-		public Configuration(final byte[] input, final List<byte[]> output) {
-			final IPipeFactory pipeFactory = PIPE_FACTORY_REGISTRY.getPipeFactory(ThreadCommunication.INTRA, PipeOrdering.ARBITRARY, false);
-
-			final InitialElementProducer<byte[]> producer = new InitialElementProducer<byte[]>(input);
-			final CipherByteArray encryptStage = new CipherByteArray("somePassword", CipherMode.ENCRYPT);
-			final CipherByteArray decryptStage = new CipherByteArray("somePassword", CipherMode.DECRYPT);
-			final CollectorSink<byte[]> sink = new CollectorSink<byte[]>(output);
-
-			pipeFactory.create(producer.getOutputPort(), encryptStage.getInputPort());
-			pipeFactory.create(encryptStage.getOutputPort(), decryptStage.getInputPort());
-			pipeFactory.create(decryptStage.getOutputPort(), sink.getInputPort());
+		final byte[] input = new byte[] { 1, 2, 3, 4, 5 };
+		final List<byte[]> encryptedResult = new ArrayList<byte[]>();
+		final List<byte[]> decryptedResult = new ArrayList<byte[]>();
 
-			super.addThreadableStage(producer);
-		}
+		test(encryptStage).and().send(input).to(encryptStage.getInputPort()).and().receive(encryptedResult).from(encryptStage.getOutputPort()).start();
+		test(decryptStage).and().send(encryptedResult).to(decryptStage.getInputPort()).and().receive(decryptedResult).from(decryptStage.getOutputPort()).start();
 
+		assertThat(decryptedResult, contains(input));
 	}
 
 }
diff --git a/src/test/java/teetime/stage/EveryXthStageTest.java b/src/test/java/teetime/stage/EveryXthStageTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..72b965b70f08db5ae31d3e40fb55ae45d9a5a6f4
--- /dev/null
+++ b/src/test/java/teetime/stage/EveryXthStageTest.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2015 TeeTime (http://teetime.sourceforge.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package teetime.stage;
+
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static teetime.framework.test.StageTester.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Nils Christian Ehmke
+ */
+public class EveryXthStageTest {
+
+	private EveryXthStage<Integer> stage;
+	private List<Integer> results;
+
+	@Before
+	public void initializeStage() {
+		this.stage = new EveryXthStage<Integer>(5);
+		this.results = new ArrayList<Integer>();
+	}
+
+	@Test
+	public void notEnoughInputShouldResultInEmptyOutput() {
+		test(this.stage).and().send(1, 5, 9, 10).to(this.stage.getInputPort()).and().receive(this.results).from(this.stage.getOutputPort()).start();
+
+		assertThat(this.results, is(empty()));
+	}
+
+	@Test
+	public void enoughInputShouldResultInCounterValuesBeingSend() {
+		test(this.stage).and().send(1, 5, 9, 10, 8).to(this.stage.getInputPort()).and().receive(this.results).from(this.stage.getOutputPort()).start();
+
+		assertThat(this.results, contains(5));
+	}
+
+}
diff --git a/src/test/java/teetime/stage/InstanceCounterTest.java b/src/test/java/teetime/stage/InstanceCounterTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d533085c63949b296d2a231c99ed00fad94d6f56
--- /dev/null
+++ b/src/test/java/teetime/stage/InstanceCounterTest.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2015 TeeTime (http://teetime.sourceforge.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package teetime.stage;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static teetime.framework.test.StageTester.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Nils Christian Ehmke
+ */
+public class InstanceCounterTest {
+
+	private InstanceCounter<Object, Clazz> filter;
+
+	@Before
+	public void initializeFilter() {
+		this.filter = new InstanceCounter<Object, Clazz>(Clazz.class);
+	}
+
+	@Test
+	public void filterShouldCountCorrectTypes() {
+		final Object clazz = new Clazz();
+
+		test(this.filter).and().send(clazz).to(this.filter.getInputPort()).start();
+
+		assertThat(this.filter.getCounter(), is(1));
+	}
+
+	@Test
+	public void filterShouldCountSubTypes() {
+		final Object clazz = new SubClazz();
+
+		test(this.filter).and().send(clazz).to(this.filter.getInputPort()).start();
+
+		assertThat(this.filter.getCounter(), is(1));
+	}
+
+	@Test
+	public void filterShouldDropInvalidTypes() {
+		final Object object = new Object();
+
+		test(this.filter).and().send(object).to(this.filter.getInputPort()).start();
+
+		assertThat(this.filter.getCounter(), is(0));
+	}
+
+	@Test
+	public void filterShouldWorkWithMultipleInput() {
+		final List<Object> input = new ArrayList<Object>();
+
+		input.add(new Object());
+		input.add(new Clazz());
+		input.add(new Object());
+		input.add(new SubClazz());
+		input.add(new Object());
+
+		test(this.filter).and().send(input).to(this.filter.getInputPort()).start();
+
+		assertThat(this.filter.getCounter(), is(2));
+	}
+
+	private static class Clazz {
+	}
+
+	private static class SubClazz extends Clazz {
+	}
+
+}
diff --git a/src/test/java/teetime/stage/ObjectProducerTest.java b/src/test/java/teetime/stage/ObjectProducerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a623248aff4764344be81c1ae25167c3aee2b6d0
--- /dev/null
+++ b/src/test/java/teetime/stage/ObjectProducerTest.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2015 TeeTime (http://teetime.sourceforge.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package teetime.stage;
+
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static teetime.framework.test.StageTester.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import teetime.util.ConstructorClosure;
+
+/**
+ * @author Nils Christian Ehmke
+ */
+public class ObjectProducerTest {
+
+	@Test
+	public void producerShouldSendSpecifiedNumberOfObjects() {
+		final List<Integer> results = new ArrayList<Integer>();
+		final ObjectProducer<Integer> producer = new ObjectProducer<Integer>(3, new Generator());
+
+		test(producer).and().receive(results).from(producer.getOutputPort()).start();
+
+		assertThat(results, contains(1, 2, 3));
+	}
+
+	public void producerShouldSendNothingIfSpecified() {
+		final List<Integer> results = new ArrayList<Integer>();
+		final ObjectProducer<Integer> producer = new ObjectProducer<Integer>(0, new Generator());
+
+		test(producer).and().receive(results).from(producer.getOutputPort()).start();
+
+		assertThat(results, is(empty()));
+	}
+
+	private static class Generator implements ConstructorClosure<Integer> {
+
+		private int counter = 1;
+
+		@Override
+		public Integer create() {
+			return new Integer(this.counter++);
+		}
+	}
+
+}
diff --git a/src/test/java/teetime/stage/RelayTest.java b/src/test/java/teetime/stage/RelayTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2304be43c8b37840d37b3a280df1651bab27273c
--- /dev/null
+++ b/src/test/java/teetime/stage/RelayTest.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2015 TeeTime (http://teetime.sourceforge.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package teetime.stage;
+
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.junit.Assert.assertThat;
+import static teetime.framework.test.StageTester.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * @author Nils Christian Ehmke
+ */
+public class RelayTest {
+
+	@Test
+	public void relayFilterShouldForwardInput() {
+		final List<Integer> results = new ArrayList<Integer>();
+		final Relay<Integer> relay = new Relay<Integer>();
+
+		test(relay).and().send(1, 2, 3).to(relay.getInputPort()).and().receive(results).from(relay.getOutputPort()).start();
+
+		assertThat(results, contains(1, 2, 3));
+	}
+
+}