diff --git a/tests/Formats/AtomFormatTest.php b/tests/Formats/AtomFormatTest.php
index 73268cb7..a871ea86 100644
--- a/tests/Formats/AtomFormatTest.php
+++ b/tests/Formats/AtomFormatTest.php
@@ -6,70 +6,22 @@
 
 namespace RssBridge\Tests\Formats;
 
-use FormatFactory;
+require_once __DIR__ . '/BaseFormatTest.php';
+
 use PHPUnit\Framework\TestCase;
 
-class AtomFormatTest extends TestCase {
-	const PATH_SAMPLES	= __DIR__ . '/samples/';
-	const PATH_EXPECTED	= __DIR__ . '/samples/expectedAtomFormat/';
-
-	private $sample;
-	private $format;
-	private $data;
+class AtomFormatTest extends BaseFormatTest {
+	private const PATH_EXPECTED = self::PATH_SAMPLES . 'expectedAtomFormat/';
 
 	/**
 	 * @dataProvider sampleProvider
 	 * @runInSeparateProcess
 	 */
-	public function testOutput($path) {
-		$this->setSample($path);
-		$this->initFormat();
+	public function testOutput(string $name, string $path) {
+		$data = $this->formatData('Atom', $this->loadSample($path));
+		$this->assertNotFalse(simplexml_load_string($data));
 
-		$this->assertXmlStringEqualsXmlFile($this->sample->expected, $this->data);
-	}
-
-	public function sampleProvider() {
-		$samples = array();
-		foreach (glob(self::PATH_SAMPLES . '*.json') as $path) {
-			$samples[basename($path, '.json')] = array($path);
-		}
-		return $samples;
-	}
-
-	private function setSample($path) {
-		$data = json_decode(file_get_contents($path), true);
-		if (isset($data['meta']) && isset($data['items'])) {
-			if (!empty($data['server']))
-				$this->setServerVars($data['server']);
-
-			$items = array();
-			foreach($data['items'] as $item) {
-				$items[] = new \FeedItem($item);
-			}
-
-			$this->sample = (object)array(
-				'meta'		=> $data['meta'],
-				'items'		=> $items,
-				'expected'	=> self::PATH_EXPECTED . basename($path, '.json') . '.xml'
-			);
-		} else {
-			$this->fail('invalid test sample: ' . basename($path, '.json'));
-		}
-	}
-
-	private function setServerVars($list) {
-		$_SERVER = array_merge($_SERVER, $list);
-	}
-
-	private function initFormat() {
-		$formatFac = new FormatFactory();
-		$formatFac->setWorkingDir(PATH_LIB_FORMATS);
-		$this->format = $formatFac->create('Atom');
-		$this->format->setItems($this->sample->items);
-		$this->format->setExtraInfos($this->sample->meta);
-		$this->format->setLastModified(strtotime('2000-01-01 12:00:00 UTC'));
-
-		$this->data = $this->format->stringify();
-		$this->assertNotFalse(simplexml_load_string($this->data));
+		$expected = self::PATH_EXPECTED . $name . '.xml';
+		$this->assertXmlStringEqualsXmlFile($expected, $data);
 	}
 }
diff --git a/tests/Formats/BaseFormatTest.php b/tests/Formats/BaseFormatTest.php
new file mode 100644
index 00000000..dac0e341
--- /dev/null
+++ b/tests/Formats/BaseFormatTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace RssBridge\Tests\Formats;
+
+use PHPUnit\Framework\TestCase;
+use FormatFactory;
+
+abstract class BaseFormatTest extends TestCase {
+	protected const PATH_SAMPLES = __DIR__ . '/samples/';
+
+	/**
+	 * @return array<string, array{string, string}>
+	 */
+	public function sampleProvider() {
+		$samples = [];
+		foreach (glob(self::PATH_SAMPLES . '*.json') as $path) {
+			$name = basename($path, '.json');
+			$samples[$name] = [
+				$name,
+				$path,
+			];
+		}
+		return $samples;
+	}
+
+	/**
+	 * Cannot be part of the sample returned by sampleProvider since this modifies $_SERVER
+	 * and thus needs to be run in a separate process to avoid side effects.
+	 */
+	protected function loadSample(string $path): \stdClass {
+		$data = json_decode(file_get_contents($path), true);
+		if (isset($data['meta']) && isset($data['items'])) {
+			if (!empty($data['server']))
+				$this->setServerVars($data['server']);
+
+			$items = array();
+			foreach($data['items'] as $item) {
+				$items[] = new \FeedItem($item);
+			}
+
+			return (object)array(
+				'meta' => $data['meta'],
+				'items' => $items,
+			);
+		} else {
+			$this->fail('invalid test sample: ' . basename($path, '.json'));
+		}
+	}
+
+	private function setServerVars(array $list): void {
+		$_SERVER = array_merge($_SERVER, $list);
+	}
+
+	protected function formatData(string $formatName, \stdClass $sample): string {
+		$formatFac = new FormatFactory();
+		$formatFac->setWorkingDir(PATH_LIB_FORMATS);
+		$format = $formatFac->create($formatName);
+		$format->setItems($sample->items);
+		$format->setExtraInfos($sample->meta);
+		$format->setLastModified(strtotime('2000-01-01 12:00:00 UTC'));
+
+		return $format->stringify();
+	}
+}
diff --git a/tests/Formats/JsonFormatTest.php b/tests/Formats/JsonFormatTest.php
index d99c55b3..3b9f8d47 100644
--- a/tests/Formats/JsonFormatTest.php
+++ b/tests/Formats/JsonFormatTest.php
@@ -6,70 +6,22 @@
 
 namespace RssBridge\Tests\Formats;
 
-use FormatFactory;
+require_once __DIR__ . '/BaseFormatTest.php';
+
 use PHPUnit\Framework\TestCase;
 
-class JsonFormatTest extends TestCase {
-	const PATH_SAMPLES	= __DIR__ . '/samples/';
-	const PATH_EXPECTED	= __DIR__ . '/samples/expectedJsonFormat/';
-
-	private $sample;
-	private $format;
-	private $data;
+class JsonFormatTest extends BaseFormatTest {
+	private const PATH_EXPECTED = self::PATH_SAMPLES . 'expectedJsonFormat/';
 
 	/**
 	 * @dataProvider sampleProvider
 	 * @runInSeparateProcess
 	 */
-	public function testOutput($path) {
-		$this->setSample($path);
-		$this->initFormat();
+	public function testOutput(string $name, string $path) {
+		$data = $this->formatData('Json', $this->loadSample($path));
+		$this->assertNotNull(json_decode($data), 'invalid JSON output: ' . json_last_error_msg());
 
-		$this->assertJsonStringEqualsJsonFile($this->sample->expected, $this->data);
-	}
-
-	public function sampleProvider() {
-		$samples = array();
-		foreach (glob(self::PATH_SAMPLES . '*.json') as $path) {
-			$samples[basename($path, '.json')] = array($path);
-		}
-		return $samples;
-	}
-
-	private function setSample($path) {
-		$data = json_decode(file_get_contents($path), true);
-		if (isset($data['meta']) && isset($data['items'])) {
-			if (!empty($data['server']))
-				$this->setServerVars($data['server']);
-
-			$items = array();
-			foreach($data['items'] as $item) {
-				$items[] = new \FeedItem($item);
-			}
-
-			$this->sample = (object)array(
-				'meta'		=> $data['meta'],
-				'items'		=> $items,
-				'expected'	=> self::PATH_EXPECTED . basename($path)
-			);
-		} else {
-			$this->fail('invalid test sample: ' . basename($path, '.json'));
-		}
-	}
-
-	private function setServerVars($list) {
-		$_SERVER = array_merge($_SERVER, $list);
-	}
-
-	private function initFormat() {
-		$formatFac = new FormatFactory();
-		$formatFac->setWorkingDir(PATH_LIB_FORMATS);
-		$this->format = $formatFac->create('Json');
-		$this->format->setItems($this->sample->items);
-		$this->format->setExtraInfos($this->sample->meta);
-		$this->format->setLastModified(strtotime('2000-01-01 12:00:00 UTC'));
-
-		$this->data = $this->format->stringify();
-		$this->assertNotNull(json_decode($this->data), 'invalid JSON output: ' . json_last_error_msg());
+		$expected = self::PATH_EXPECTED . $name . '.json';
+		$this->assertJsonStringEqualsJsonFile($expected, $data);
 	}
 }
diff --git a/tests/Formats/MrssFormatTest.php b/tests/Formats/MrssFormatTest.php
index 3becd8eb..6def6afb 100644
--- a/tests/Formats/MrssFormatTest.php
+++ b/tests/Formats/MrssFormatTest.php
@@ -7,70 +7,22 @@
 
 namespace RssBridge\Tests\Formats;
 
-use FormatFactory;
+require_once __DIR__ . '/BaseFormatTest.php';
+
 use PHPUnit\Framework\TestCase;
 
-class MrssFormatTest extends TestCase {
-	const PATH_SAMPLES	= __DIR__ . '/samples/';
-	const PATH_EXPECTED	= __DIR__ . '/samples/expectedMrssFormat/';
-
-	private $sample;
-	private $format;
-	private $data;
+class MrssFormatTest extends BaseFormatTest {
+	private const PATH_EXPECTED = self::PATH_SAMPLES . 'expectedMrssFormat/';
 
 	/**
 	 * @dataProvider sampleProvider
 	 * @runInSeparateProcess
 	 */
-	public function testOutput($path) {
-		$this->setSample($path);
-		$this->initFormat();
+	public function testOutput(string $name, string $path) {
+		$data = $this->formatData('Mrss', $this->loadSample($path));
+		$this->assertNotFalse(simplexml_load_string($data));
 
-		$this->assertXmlStringEqualsXmlFile($this->sample->expected, $this->data);
-	}
-
-	public function sampleProvider() {
-		$samples = array();
-		foreach (glob(self::PATH_SAMPLES . '*.json') as $path) {
-			$samples[basename($path, '.json')] = array($path);
-		}
-		return $samples;
-	}
-
-	private function setSample($path) {
-		$data = json_decode(file_get_contents($path), true);
-		if (isset($data['meta']) && isset($data['items'])) {
-			if (!empty($data['server']))
-				$this->setServerVars($data['server']);
-
-			$items = array();
-			foreach($data['items'] as $item) {
-				$items[] = new \FeedItem($item);
-			}
-
-			$this->sample = (object)array(
-				'meta'		=> $data['meta'],
-				'items'		=> $items,
-				'expected'	=> self::PATH_EXPECTED . basename($path, '.json') . '.xml'
-			);
-		} else {
-			$this->fail('invalid test sample: ' . basename($path, '.json'));
-		}
-	}
-
-	private function setServerVars($list) {
-		$_SERVER = array_merge($_SERVER, $list);
-	}
-
-	private function initFormat() {
-		$formatFac = new FormatFactory();
-		$formatFac->setWorkingDir(PATH_LIB_FORMATS);
-		$this->format = $formatFac->create('Mrss');
-		$this->format->setItems($this->sample->items);
-		$this->format->setExtraInfos($this->sample->meta);
-		$this->format->setLastModified(strtotime('2000-01-01 12:00:00 UTC'));
-
-		$this->data = $this->format->stringify();
-		$this->assertNotFalse(simplexml_load_string($this->data));
+		$expected = self::PATH_EXPECTED . $name . '.xml';
+		$this->assertXmlStringEqualsXmlFile($expected, $data);
 	}
 }