From: Joe Nahmias <jello@debian.org>
Date: Sun, 14 Sep 2025 22:44:45 -0400
Subject: change abstract class suffix to TestCase; rename files

---
 phpunit.xml.dist                                   |    4 -
 .../AbstractStreamBufferAcceptanceTest.php         |  143 ---
 .../AbstractStreamBufferAcceptanceTestCase.php     |  143 +++
 tests/unit/Swift/Mime/AbstractMimeEntityTest.php   | 1092 ----------------
 .../unit/Swift/Mime/AbstractMimeEntityTestCase.php | 1092 ++++++++++++++++
 .../Transport/AbstractSmtpEventSupportTest.php     |  558 ---------
 .../Transport/AbstractSmtpEventSupportTestCase.php |  558 +++++++++
 tests/unit/Swift/Transport/AbstractSmtpTest.php    | 1311 --------------------
 .../unit/Swift/Transport/AbstractSmtpTestCase.php  | 1311 ++++++++++++++++++++
 9 files changed, 3104 insertions(+), 3108 deletions(-)
 delete mode 100644 tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php
 create mode 100644 tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTestCase.php
 delete mode 100644 tests/unit/Swift/Mime/AbstractMimeEntityTest.php
 create mode 100644 tests/unit/Swift/Mime/AbstractMimeEntityTestCase.php
 delete mode 100644 tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php
 create mode 100644 tests/unit/Swift/Transport/AbstractSmtpEventSupportTestCase.php
 delete mode 100644 tests/unit/Swift/Transport/AbstractSmtpTest.php
 create mode 100644 tests/unit/Swift/Transport/AbstractSmtpTestCase.php

diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 01b1058..8bd1d88 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -18,13 +18,9 @@
     <testsuites>
         <testsuite name="SwiftMailer unit tests">
             <directory>tests/unit</directory>
-            <exclude>tests/unit/Swift/Mime/AbstractMimeEntityTest.php</exclude>
-            <exclude>tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php</exclude>
-            <exclude>tests/unit/Swift/Transport/AbstractSmtpTest.php</exclude>
         </testsuite>
         <testsuite name="SwiftMailer acceptance tests">
             <directory>tests/acceptance</directory>
-            <exclude>tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php</exclude>
         </testsuite>
         <testsuite name="SwiftMailer bug">
             <directory>tests/bug</directory>
diff --git a/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php b/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php
deleted file mode 100644
index 6b91b9b..0000000
--- a/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-
-abstract class Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest extends \PHPUnit\Framework\TestCase
-{
-    protected $buffer;
-
-    abstract protected function initializeBuffer();
-
-    protected function setUp(): void
-    {
-        if (true == getenv('TRAVIS')) {
-            $this->markTestSkipped(
-                'Will fail on travis-ci if not skipped due to travis blocking '.
-                'socket mailing tcp connections.'
-             );
-        }
-
-        $this->buffer = new Swift_Transport_StreamBuffer(
-            $this->createMock('Swift_ReplacementFilterFactory')
-        );
-    }
-
-    public function testReadLine()
-    {
-        $this->initializeBuffer();
-
-        $line = $this->buffer->readLine(0);
-        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
-        $seq = $this->buffer->write("QUIT\r\n");
-        $this->assertTrue((bool) $seq);
-        $line = $this->buffer->readLine($seq);
-        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
-        $this->buffer->terminate();
-    }
-
-    public function testWrite()
-    {
-        $this->initializeBuffer();
-
-        $line = $this->buffer->readLine(0);
-        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
-
-        $seq = $this->buffer->write("HELO foo\r\n");
-        $this->assertTrue((bool) $seq);
-        $line = $this->buffer->readLine($seq);
-        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
-
-        $seq = $this->buffer->write("QUIT\r\n");
-        $this->assertTrue((bool) $seq);
-        $line = $this->buffer->readLine($seq);
-        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
-        $this->buffer->terminate();
-    }
-
-    public function testBindingOtherStreamsMirrorsWriteOperations()
-    {
-        $this->initializeBuffer();
-
-        $is1 = $this->createMockInputStream();
-        $is2 = $this->createMockInputStream();
-        $matcher = $this->exactly(2);
-
-        $is1->expects($matcher)
-            ->method('write')->willReturnCallback(function (...$parameters) use ($matcher) {
-            if ($matcher->numberOfInvocations() === 1) {
-                $this->assertSame('x', $parameters[0]);
-            }
-            if ($matcher->numberOfInvocations() === 2) {
-                $this->assertSame('y', $parameters[0]);
-            }
-        });
-        $matcher = $this->exactly(2);
-        $is2->expects($matcher)
-            ->method('write')->willReturnCallback(function (...$parameters) use ($matcher) {
-            if ($matcher->numberOfInvocations() === 1) {
-                $this->assertSame('x', $parameters[0]);
-            }
-            if ($matcher->numberOfInvocations() === 2) {
-                $this->assertSame('y', $parameters[0]);
-            }
-        });
-
-        $this->buffer->bind($is1);
-        $this->buffer->bind($is2);
-
-        $this->buffer->write('x');
-        $this->buffer->write('y');
-    }
-
-    public function testBindingOtherStreamsMirrorsFlushOperations()
-    {
-        $this->initializeBuffer();
-
-        $is1 = $this->createMockInputStream();
-        $is2 = $this->createMockInputStream();
-
-        $is1->expects($this->once())
-            ->method('flushBuffers');
-        $is2->expects($this->once())
-            ->method('flushBuffers');
-
-        $this->buffer->bind($is1);
-        $this->buffer->bind($is2);
-
-        $this->buffer->flushBuffers();
-    }
-
-    public function testUnbindingStreamPreventsFurtherWrites()
-    {
-        $this->initializeBuffer();
-
-        $is1 = $this->createMockInputStream();
-        $is2 = $this->createMockInputStream();
-        $matcher = $this->exactly(2);
-
-        $is1->expects($matcher)
-            ->method('write')->willReturnCallback(function (...$parameters) use ($matcher) {
-            if ($matcher->numberOfInvocations() === 1) {
-                $this->assertSame('x', $parameters[0]);
-            }
-            if ($matcher->numberOfInvocations() === 2) {
-                $this->assertSame('y', $parameters[0]);
-            }
-        });
-        $is2->expects($this->once())
-            ->method('write')
-            ->with('x');
-
-        $this->buffer->bind($is1);
-        $this->buffer->bind($is2);
-
-        $this->buffer->write('x');
-
-        $this->buffer->unbind($is2);
-
-        $this->buffer->write('y');
-    }
-
-    private function createMockInputStream()
-    {
-        return $this->createMock('Swift_InputByteStream');
-    }
-}
diff --git a/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTestCase.php b/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTestCase.php
new file mode 100644
index 0000000..9e63e8d
--- /dev/null
+++ b/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTestCase.php
@@ -0,0 +1,143 @@
+<?php
+
+abstract class Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTestCase extends \PHPUnit\Framework\TestCase
+{
+    protected $buffer;
+
+    abstract protected function initializeBuffer();
+
+    protected function setUp(): void
+    {
+        if (true == getenv('TRAVIS')) {
+            $this->markTestSkipped(
+                'Will fail on travis-ci if not skipped due to travis blocking '.
+                'socket mailing tcp connections.'
+             );
+        }
+
+        $this->buffer = new Swift_Transport_StreamBuffer(
+            $this->createMock('Swift_ReplacementFilterFactory')
+        );
+    }
+
+    public function testReadLine()
+    {
+        $this->initializeBuffer();
+
+        $line = $this->buffer->readLine(0);
+        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
+        $seq = $this->buffer->write("QUIT\r\n");
+        $this->assertTrue((bool) $seq);
+        $line = $this->buffer->readLine($seq);
+        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
+        $this->buffer->terminate();
+    }
+
+    public function testWrite()
+    {
+        $this->initializeBuffer();
+
+        $line = $this->buffer->readLine(0);
+        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
+
+        $seq = $this->buffer->write("HELO foo\r\n");
+        $this->assertTrue((bool) $seq);
+        $line = $this->buffer->readLine($seq);
+        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
+
+        $seq = $this->buffer->write("QUIT\r\n");
+        $this->assertTrue((bool) $seq);
+        $line = $this->buffer->readLine($seq);
+        $this->assertMatchesRegularExpression('/^[0-9]{3}.*?\r\n$/D', $line);
+        $this->buffer->terminate();
+    }
+
+    public function testBindingOtherStreamsMirrorsWriteOperations()
+    {
+        $this->initializeBuffer();
+
+        $is1 = $this->createMockInputStream();
+        $is2 = $this->createMockInputStream();
+        $matcher = $this->exactly(2);
+
+        $is1->expects($matcher)
+            ->method('write')->willReturnCallback(function (...$parameters) use ($matcher) {
+            if ($matcher->numberOfInvocations() === 1) {
+                $this->assertSame('x', $parameters[0]);
+            }
+            if ($matcher->numberOfInvocations() === 2) {
+                $this->assertSame('y', $parameters[0]);
+            }
+        });
+        $matcher = $this->exactly(2);
+        $is2->expects($matcher)
+            ->method('write')->willReturnCallback(function (...$parameters) use ($matcher) {
+            if ($matcher->numberOfInvocations() === 1) {
+                $this->assertSame('x', $parameters[0]);
+            }
+            if ($matcher->numberOfInvocations() === 2) {
+                $this->assertSame('y', $parameters[0]);
+            }
+        });
+
+        $this->buffer->bind($is1);
+        $this->buffer->bind($is2);
+
+        $this->buffer->write('x');
+        $this->buffer->write('y');
+    }
+
+    public function testBindingOtherStreamsMirrorsFlushOperations()
+    {
+        $this->initializeBuffer();
+
+        $is1 = $this->createMockInputStream();
+        $is2 = $this->createMockInputStream();
+
+        $is1->expects($this->once())
+            ->method('flushBuffers');
+        $is2->expects($this->once())
+            ->method('flushBuffers');
+
+        $this->buffer->bind($is1);
+        $this->buffer->bind($is2);
+
+        $this->buffer->flushBuffers();
+    }
+
+    public function testUnbindingStreamPreventsFurtherWrites()
+    {
+        $this->initializeBuffer();
+
+        $is1 = $this->createMockInputStream();
+        $is2 = $this->createMockInputStream();
+        $matcher = $this->exactly(2);
+
+        $is1->expects($matcher)
+            ->method('write')->willReturnCallback(function (...$parameters) use ($matcher) {
+            if ($matcher->numberOfInvocations() === 1) {
+                $this->assertSame('x', $parameters[0]);
+            }
+            if ($matcher->numberOfInvocations() === 2) {
+                $this->assertSame('y', $parameters[0]);
+            }
+        });
+        $is2->expects($this->once())
+            ->method('write')
+            ->with('x');
+
+        $this->buffer->bind($is1);
+        $this->buffer->bind($is2);
+
+        $this->buffer->write('x');
+
+        $this->buffer->unbind($is2);
+
+        $this->buffer->write('y');
+    }
+
+    private function createMockInputStream()
+    {
+        return $this->createMock('Swift_InputByteStream');
+    }
+}
diff --git a/tests/unit/Swift/Mime/AbstractMimeEntityTest.php b/tests/unit/Swift/Mime/AbstractMimeEntityTest.php
deleted file mode 100644
index d9a86fa..0000000
--- a/tests/unit/Swift/Mime/AbstractMimeEntityTest.php
+++ /dev/null
@@ -1,1092 +0,0 @@
-<?php
-
-require_once \dirname(__DIR__, 3).'/fixtures/MimeEntityFixture.php';
-
-abstract class Swift_Mime_AbstractMimeEntityTest extends \SwiftMailerTestCase
-{
-    public function testGetHeadersReturnsHeaderSet()
-    {
-        $headers = $this->createHeaderSet();
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $this->assertSame($headers, $entity->getHeaders());
-    }
-
-    public function testContentTypeIsReturnedFromHeader()
-    {
-        $ctype = $this->createHeader('Content-Type', 'image/jpeg-test');
-        $headers = $this->createHeaderSet(['Content-Type' => $ctype]);
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $this->assertEquals('image/jpeg-test', $entity->getContentType());
-    }
-
-    public function testContentTypeIsSetInHeader()
-    {
-        $ctype = $this->createHeader('Content-Type', 'text/plain', [], false);
-        $headers = $this->createHeaderSet(['Content-Type' => $ctype]);
-
-        $ctype->shouldReceive('setFieldBodyModel')
-              ->once()
-              ->with('image/jpeg');
-        $ctype->shouldReceive('setFieldBodyModel')
-              ->zeroOrMoreTimes()
-              ->with(\Mockery::not('image/jpeg'));
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setContentType('image/jpeg');
-    }
-
-    public function testContentTypeHeaderIsAddedIfNoneSet()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('addParameterizedHeader')
-                ->once()
-                ->with('Content-Type', 'image/jpeg');
-        $headers->shouldReceive('addParameterizedHeader')
-                ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setContentType('image/jpeg');
-    }
-
-    public function testContentTypeCanBeSetViaSetBody()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('addParameterizedHeader')
-                ->once()
-                ->with('Content-Type', 'text/html');
-        $headers->shouldReceive('addParameterizedHeader')
-                ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setBody('<b>foo</b>', 'text/html');
-    }
-
-    public function testGetEncoderFromConstructor()
-    {
-        $encoder = $this->createEncoder('base64');
-        $entity = $this->createEntity($this->createHeaderSet(), $encoder,
-            $this->createCache()
-            );
-        $this->assertSame($encoder, $entity->getEncoder());
-    }
-
-    public function testSetAndGetEncoder()
-    {
-        $encoder = $this->createEncoder('base64');
-        $headers = $this->createHeaderSet();
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setEncoder($encoder);
-        $this->assertSame($encoder, $entity->getEncoder());
-    }
-
-    public function testSettingEncoderUpdatesTransferEncoding()
-    {
-        $encoder = $this->createEncoder('base64');
-        $encoding = $this->createHeader(
-            'Content-Transfer-Encoding', '8bit', [], false
-            );
-        $headers = $this->createHeaderSet([
-            'Content-Transfer-Encoding' => $encoding,
-            ]);
-        $encoding->shouldReceive('setFieldBodyModel')
-                 ->once()
-                 ->with('base64');
-        $encoding->shouldReceive('setFieldBodyModel')
-                 ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setEncoder($encoder);
-    }
-
-    public function testSettingEncoderAddsEncodingHeaderIfNonePresent()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('addTextHeader')
-                ->once()
-                ->with('Content-Transfer-Encoding', 'something');
-        $headers->shouldReceive('addTextHeader')
-                ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setEncoder($this->createEncoder('something'));
-    }
-
-    public function testIdIsReturnedFromHeader()
-    {
-        /* -- RFC 2045, 7.
-        In constructing a high-level user agent, it may be desirable to allow
-        one body to make reference to another.  Accordingly, bodies may be
-        labelled using the "Content-ID" header field, which is syntactically
-        identical to the "Message-ID" header field
-        */
-
-        $cid = $this->createHeader('Content-ID', 'zip@button');
-        $headers = $this->createHeaderSet(['Content-ID' => $cid]);
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $this->assertEquals('zip@button', $entity->getId());
-    }
-
-    public function testIdIsSetInHeader()
-    {
-        $cid = $this->createHeader('Content-ID', 'zip@button', [], false);
-        $headers = $this->createHeaderSet(['Content-ID' => $cid]);
-
-        $cid->shouldReceive('setFieldBodyModel')
-            ->once()
-            ->with('foo@bar');
-        $cid->shouldReceive('setFieldBodyModel')
-            ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setId('foo@bar');
-    }
-
-    public function testIdIsAutoGenerated()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $this->assertMatchesRegularExpression('/^.*?@.*?$/D', $entity->getId());
-    }
-
-    public function testGenerateIdCreatesNewId()
-    {
-        $headers = $this->createHeaderSet();
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $id1 = $entity->generateId();
-        $id2 = $entity->generateId();
-        $this->assertNotEquals($id1, $id2);
-    }
-
-    public function testGenerateIdSetsNewId()
-    {
-        $headers = $this->createHeaderSet();
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $id = $entity->generateId();
-        $this->assertEquals($id, $entity->getId());
-    }
-
-    public function testDescriptionIsReadFromHeader()
-    {
-        /* -- RFC 2045, 8.
-        The ability to associate some descriptive information with a given
-        body is often desirable.  For example, it may be useful to mark an
-        "image" body as "a picture of the Space Shuttle Endeavor."  Such text
-        may be placed in the Content-Description header field.  This header
-        field is always optional.
-        */
-
-        $desc = $this->createHeader('Content-Description', 'something');
-        $headers = $this->createHeaderSet(['Content-Description' => $desc]);
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $this->assertEquals('something', $entity->getDescription());
-    }
-
-    public function testDescriptionIsSetInHeader()
-    {
-        $desc = $this->createHeader('Content-Description', '', [], false);
-        $desc->shouldReceive('setFieldBodyModel')->once()->with('whatever');
-
-        $headers = $this->createHeaderSet(['Content-Description' => $desc]);
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setDescription('whatever');
-    }
-
-    public function testDescriptionHeaderIsAddedIfNotPresent()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('addTextHeader')
-                ->once()
-                ->with('Content-Description', 'whatever');
-        $headers->shouldReceive('addTextHeader')
-                ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setDescription('whatever');
-    }
-
-    public function testSetAndGetMaxLineLength()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setMaxLineLength(60);
-        $this->assertEquals(60, $entity->getMaxLineLength());
-    }
-
-    public function testEncoderIsUsedForStringGeneration()
-    {
-        $encoder = $this->createEncoder('base64', false);
-        $encoder->expects($this->once())
-                ->method('encodeString')
-                ->with('blah');
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $encoder, $this->createCache()
-            );
-        $entity->setBody('blah');
-        $entity->toString();
-    }
-
-    public function testMaxLineLengthIsProvidedWhenEncoding()
-    {
-        $encoder = $this->createEncoder('base64', false);
-        $encoder->expects($this->once())
-                ->method('encodeString')
-                ->with('blah', 0, 65);
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $encoder, $this->createCache()
-            );
-        $entity->setBody('blah');
-        $entity->setMaxLineLength(65);
-        $entity->toString();
-    }
-
-    public function testHeadersAppearInString()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->once()
-                ->andReturn(
-                    "Content-Type: text/plain; charset=utf-8\r\n".
-                    "X-MyHeader: foobar\r\n"
-                );
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $this->assertEquals(
-            "Content-Type: text/plain; charset=utf-8\r\n".
-            "X-MyHeader: foobar\r\n",
-            $entity->toString()
-            );
-    }
-
-    public function testSetAndGetBody()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setBody("blah\r\nblah!");
-        $this->assertEquals("blah\r\nblah!", $entity->getBody());
-    }
-
-    public function testBodyIsAppended()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->once()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setBody("blah\r\nblah!");
-        $this->assertEquals(
-            "Content-Type: text/plain; charset=utf-8\r\n".
-            "\r\n".
-            "blah\r\nblah!",
-            $entity->toString()
-            );
-    }
-
-    public function testGetBodyReturnsStringFromByteStream()
-    {
-        $os = $this->createOutputStream('byte stream string');
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setBody($os);
-        $this->assertEquals('byte stream string', $entity->getBody());
-    }
-
-    public function testByteStreamBodyIsAppended()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $os = $this->createOutputStream('streamed');
-        $headers->shouldReceive('toString')
-                ->once()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setBody($os);
-        $this->assertEquals(
-            "Content-Type: text/plain; charset=utf-8\r\n".
-            "\r\n".
-            'streamed',
-            $entity->toString()
-            );
-    }
-
-    public function testBoundaryCanBeRetrieved()
-    {
-        /* -- RFC 2046, 5.1.1.
-     boundary := 0*69<bchars> bcharsnospace
-
-     bchars := bcharsnospace / " "
-
-     bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
-                                            "+" / "_" / "," / "-" / "." /
-                                            "/" / ":" / "=" / "?"
-        */
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $this->assertMatchesRegularExpression(
-            '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D',
-            $entity->getBoundary()
-            );
-    }
-
-    public function testBoundaryNeverChanges()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $firstBoundary = $entity->getBoundary();
-        for ($i = 0; $i < 10; ++$i) {
-            $this->assertEquals($firstBoundary, $entity->getBoundary());
-        }
-    }
-
-    public function testBoundaryCanBeSet()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setBoundary('foobar');
-        $this->assertEquals('foobar', $entity->getBoundary());
-    }
-
-    public function testAddingChildrenGeneratesBoundaryInHeaders()
-    {
-        $child = $this->createChild();
-        $cType = $this->createHeader('Content-Type', 'text/plain', [], false);
-        $cType->shouldReceive('setParameter')
-              ->once()
-              ->with('boundary', \Mockery::any());
-        $cType->shouldReceive('setParameter')
-              ->zeroOrMoreTimes();
-
-        $entity = $this->createEntity($this->createHeaderSet([
-            'Content-Type' => $cType,
-            ]),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setChildren([$child]);
-    }
-
-    public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed()
-    {
-        for ($level = Swift_Mime_SimpleMimeEntity::LEVEL_MIXED;
-            $level > Swift_Mime_SimpleMimeEntity::LEVEL_TOP; $level /= 2) {
-            $child = $this->createChild($level);
-            $cType = $this->createHeader(
-                'Content-Type', 'text/plain', [], false
-                );
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->once()
-                  ->with('multipart/mixed');
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->zeroOrMoreTimes();
-
-            $entity = $this->createEntity($this->createHeaderSet([
-                'Content-Type' => $cType, ]),
-                $this->createEncoder(), $this->createCache()
-                );
-            $entity->setChildren([$child]);
-        }
-    }
-
-    public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative()
-    {
-        for ($level = Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE;
-            $level > Swift_Mime_SimpleMimeEntity::LEVEL_MIXED; $level /= 2) {
-            $child = $this->createChild($level);
-            $cType = $this->createHeader(
-                'Content-Type', 'text/plain', [], false
-                );
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->once()
-                  ->with('multipart/alternative');
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->zeroOrMoreTimes();
-
-            $entity = $this->createEntity($this->createHeaderSet([
-                'Content-Type' => $cType, ]),
-                $this->createEncoder(), $this->createCache()
-                );
-            $entity->setChildren([$child]);
-        }
-    }
-
-    public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated()
-    {
-        for ($level = Swift_Mime_SimpleMimeEntity::LEVEL_RELATED;
-            $level > Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE; $level /= 2) {
-            $child = $this->createChild($level);
-            $cType = $this->createHeader(
-                'Content-Type', 'text/plain', [], false
-                );
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->once()
-                  ->with('multipart/related');
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->zeroOrMoreTimes();
-
-            $entity = $this->createEntity($this->createHeaderSet([
-                'Content-Type' => $cType, ]),
-                $this->createEncoder(), $this->createCache()
-                );
-            $entity->setChildren([$child]);
-        }
-    }
-
-    public function testHighestLevelChildDeterminesContentType()
-    {
-        $combinations = [
-            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
-                Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-                Swift_Mime_SimpleMimeEntity::LEVEL_RELATED,
-                ],
-                'type' => 'multipart/mixed',
-                ],
-            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
-                Swift_Mime_SimpleMimeEntity::LEVEL_RELATED,
-                ],
-                'type' => 'multipart/mixed',
-                ],
-            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
-                Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-                ],
-                'type' => 'multipart/mixed',
-                ],
-            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-                Swift_Mime_SimpleMimeEntity::LEVEL_RELATED,
-                ],
-                'type' => 'multipart/alternative',
-                ],
-            ];
-
-        foreach ($combinations as $combination) {
-            $children = [];
-            foreach ($combination['levels'] as $level) {
-                $children[] = $this->createChild($level);
-            }
-
-            $cType = $this->createHeader(
-                'Content-Type', 'text/plain', [], false
-                );
-            $cType->shouldReceive('setFieldBodyModel')
-                  ->once()
-                  ->with($combination['type']);
-
-            $headerSet = $this->createHeaderSet(['Content-Type' => $cType]);
-            $headerSet->shouldReceive('newInstance')
-                      ->zeroOrMoreTimes()
-                      ->andReturnUsing(function () use ($headerSet) {
-                          return $headerSet;
-                      });
-            $entity = $this->createEntity($headerSet,
-                $this->createEncoder(), $this->createCache()
-                );
-            $entity->setChildren($children);
-        }
-    }
-
-    public function testChildrenAppearNestedInString()
-    {
-        /* -- RFC 2046, 5.1.1.
-     (excerpt too verbose to paste here)
-     */
-
-        $headers = $this->createHeaderSet([], false);
-
-        $child1 = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'foobar', 'text/plain'
-            );
-
-        $child2 = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/html\r\n".
-            "\r\n".
-            '<b>foobar</b>', 'text/html'
-            );
-
-        $headers->shouldReceive('toString')
-              ->zeroOrMoreTimes()
-              ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setBoundary('xxx');
-        $entity->setChildren([$child1, $child2]);
-
-        $this->assertEquals(
-            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
-            "\r\n".
-            "\r\n--xxx\r\n".
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            "foobar\r\n".
-            "\r\n--xxx\r\n".
-            "Content-Type: text/html\r\n".
-            "\r\n".
-            "<b>foobar</b>\r\n".
-            "\r\n--xxx--\r\n",
-            $entity->toString()
-            );
-    }
-
-    public function testMixingLevelsIsHierarchical()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $newHeaders = $this->createHeaderSet([], false);
-
-        $part = $this->createChild(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'foobar'
-            );
-
-        $attachment = $this->createChild(Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
-            "Content-Type: application/octet-stream\r\n".
-            "\r\n".
-            'data'
-            );
-
-        $headers->shouldReceive('toString')
-              ->zeroOrMoreTimes()
-              ->andReturn("Content-Type: multipart/mixed; boundary=\"xxx\"\r\n");
-        $headers->shouldReceive('newInstance')
-              ->zeroOrMoreTimes()
-              ->andReturn($newHeaders);
-        $newHeaders->shouldReceive('toString')
-              ->zeroOrMoreTimes()
-              ->andReturn("Content-Type: multipart/alternative; boundary=\"yyy\"\r\n");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setBoundary('xxx');
-        $entity->setChildren([$part, $attachment]);
-
-        $this->assertMatchesRegularExpression(
-            '~^'.
-            "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n".
-            "\r\n\r\n--xxx\r\n".
-            "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n".
-            "\r\n\r\n--(.*?)\r\n".
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'foobar'.
-            "\r\n\r\n--\\1--\r\n".
-            "\r\n\r\n--xxx\r\n".
-            "Content-Type: application/octet-stream\r\n".
-            "\r\n".
-            'data'.
-            "\r\n\r\n--xxx--\r\n".
-            '$~',
-            $entity->toString()
-            );
-    }
-
-    public function testSettingEncoderNotifiesChildren()
-    {
-        $child = $this->createChild(0, '', false);
-        $encoder = $this->createEncoder('base64');
-
-        $child->shouldReceive('encoderChanged')
-              ->once()
-              ->with($encoder);
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setChildren([$child]);
-        $entity->setEncoder($encoder);
-    }
-
-    public function testReceiptOfEncoderChangeNotifiesChildren()
-    {
-        $child = $this->createChild(0, '', false);
-        $encoder = $this->createEncoder('base64');
-
-        $child->shouldReceive('encoderChanged')
-              ->once()
-              ->with($encoder);
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setChildren([$child]);
-        $entity->encoderChanged($encoder);
-    }
-
-    public function testReceiptOfCharsetChangeNotifiesChildren()
-    {
-        $child = $this->createChild(0, '', false);
-        $child->shouldReceive('charsetChanged')
-              ->once()
-              ->with('windows-874');
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $entity->setChildren([$child]);
-        $entity->charsetChanged('windows-874');
-    }
-
-    public function testEntityIsWrittenToByteStream()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $is = $this->createInputStream(false);
-        $is->expects($this->atLeastOnce())
-           ->method('write');
-
-        $entity->toByteStream($is);
-    }
-
-    public function testEntityHeadersAreComittedToByteStream()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-        $is = $this->createInputStream(false);
-        $is->expects($this->atLeastOnce())
-           ->method('write');
-        $is->expects($this->atLeastOnce())
-           ->method('commit');
-
-        $entity->toByteStream($is);
-    }
-
-    public function testOrderingTextBeforeHtml()
-    {
-        $htmlChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/html\r\n".
-            "\r\n".
-            'HTML PART',
-            'text/html'
-            );
-        $textChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'TEXT PART',
-            'text/plain'
-            );
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->zeroOrMoreTimes()
-                ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-            );
-        $entity->setBoundary('xxx');
-        $entity->setChildren([$htmlChild, $textChild]);
-
-        $this->assertEquals(
-            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
-            "\r\n\r\n--xxx\r\n".
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'TEXT PART'.
-            "\r\n\r\n--xxx\r\n".
-            "Content-Type: text/html\r\n".
-            "\r\n".
-            'HTML PART'.
-            "\r\n\r\n--xxx--\r\n",
-            $entity->toString()
-            );
-    }
-
-    public function testOrderingEqualContentTypesMaintainsOriginalOrdering()
-    {
-        $firstChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'PART 1',
-            'text/plain'
-        );
-        $secondChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'PART 2',
-            'text/plain'
-        );
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-            ->zeroOrMoreTimes()
-            ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $this->createCache()
-        );
-        $entity->setBoundary('xxx');
-        $entity->setChildren([$firstChild, $secondChild]);
-
-        $this->assertEquals(
-            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
-            "\r\n\r\n--xxx\r\n".
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'PART 1'.
-            "\r\n\r\n--xxx\r\n".
-            "Content-Type: text/plain\r\n".
-            "\r\n".
-            'PART 2'.
-            "\r\n\r\n--xxx--\r\n",
-            $entity->toString()
-        );
-    }
-
-    public function testUnsettingChildrenRestoresContentType()
-    {
-        $cType = $this->createHeader('Content-Type', 'text/plain', [], false);
-        $child = $this->createChild(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE);
-
-        $cType->shouldReceive('setFieldBodyModel')
-              ->twice()
-              ->with('image/jpeg');
-        $cType->shouldReceive('setFieldBodyModel')
-              ->once()
-              ->with('multipart/alternative');
-        $cType->shouldReceive('setFieldBodyModel')
-              ->zeroOrMoreTimes()
-              ->with(\Mockery::not('multipart/alternative', 'image/jpeg'));
-
-        $entity = $this->createEntity($this->createHeaderSet([
-            'Content-Type' => $cType,
-            ]),
-            $this->createEncoder(), $this->createCache()
-            );
-
-        $entity->setContentType('image/jpeg');
-        $entity->setChildren([$child]);
-        $entity->setChildren([]);
-    }
-
-    public function testBodyIsReadFromCacheWhenUsingToStringIfPresent()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->zeroOrMoreTimes()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $cache = $this->createCache(false);
-        $cache->shouldReceive('hasKey')
-              ->once()
-              ->with(\Mockery::any(), 'body')
-              ->andReturn(true);
-        $cache->shouldReceive('getString')
-              ->once()
-              ->with(\Mockery::any(), 'body')
-              ->andReturn("\r\ncache\r\ncache!");
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $cache
-            );
-
-        $entity->setBody("blah\r\nblah!");
-        $this->assertEquals(
-            "Content-Type: text/plain; charset=utf-8\r\n".
-            "\r\n".
-            "cache\r\ncache!",
-            $entity->toString()
-            );
-    }
-
-    public function testBodyIsAddedToCacheWhenUsingToString()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->zeroOrMoreTimes()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $cache = $this->createCache(false);
-        $cache->shouldReceive('hasKey')
-              ->once()
-              ->with(\Mockery::any(), 'body')
-              ->andReturn(false);
-        $cache->shouldReceive('setString')
-              ->once()
-              ->with(\Mockery::any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE);
-
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $cache
-            );
-
-        $entity->setBody("blah\r\nblah!");
-        $entity->toString();
-    }
-
-    public function testBodyIsClearedFromCacheIfNewBodySet()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->zeroOrMoreTimes()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $cache = $this->createCache(false);
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $cache
-            );
-
-        $entity->setBody("blah\r\nblah!");
-        $entity->toString();
-
-        // We set the expectation at this point because we only care what happens when calling setBody()
-        $cache->shouldReceive('clearKey')
-              ->once()
-              ->with(\Mockery::any(), 'body');
-
-        $entity->setBody("new\r\nnew!");
-    }
-
-    public function testBodyIsNotClearedFromCacheIfSameBodySet()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->zeroOrMoreTimes()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $cache = $this->createCache(false);
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $cache
-            );
-
-        $entity->setBody("blah\r\nblah!");
-        $entity->toString();
-
-        // We set the expectation at this point because we only care what happens when calling setBody()
-        $cache->shouldReceive('clearKey')
-              ->never();
-
-        $entity->setBody("blah\r\nblah!");
-    }
-
-    public function testBodyIsClearedFromCacheIfNewEncoderSet()
-    {
-        $headers = $this->createHeaderSet([], false);
-        $headers->shouldReceive('toString')
-                ->zeroOrMoreTimes()
-                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
-
-        $cache = $this->createCache(false);
-        $otherEncoder = $this->createEncoder();
-        $entity = $this->createEntity($headers, $this->createEncoder(),
-            $cache
-            );
-
-        $entity->setBody("blah\r\nblah!");
-        $entity->toString();
-
-        // We set the expectation at this point because we only care what happens when calling setEncoder()
-        $cache->shouldReceive('clearKey')
-              ->once()
-              ->with(\Mockery::any(), 'body');
-
-        $entity->setEncoder($otherEncoder);
-    }
-
-    public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent()
-    {
-        $is = $this->createInputStream();
-        $cache = $this->createCache(false);
-        $cache->shouldReceive('hasKey')
-              ->once()
-              ->with(\Mockery::any(), 'body')
-              ->andReturn(true);
-        $cache->shouldReceive('exportToByteStream')
-              ->once()
-              ->with(\Mockery::any(), 'body', $is);
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $cache
-            );
-        $entity->setBody('foo');
-
-        $entity->toByteStream($is);
-    }
-
-    public function testBodyIsAddedToCacheWhenUsingToByteStream()
-    {
-        $is = $this->createInputStream();
-        $cache = $this->createCache(false);
-        $cache->shouldReceive('hasKey')
-              ->once()
-              ->with(\Mockery::any(), 'body')
-              ->andReturn(false);
-        $cache->shouldReceive('getInputByteStream')
-              ->once()
-              ->with(\Mockery::any(), 'body');
-
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $cache
-            );
-        $entity->setBody('foo');
-
-        $entity->toByteStream($is);
-    }
-
-    public function testFluidInterface()
-    {
-        $entity = $this->createEntity($this->createHeaderSet(),
-            $this->createEncoder(), $this->createCache()
-            );
-
-        $this->assertSame($entity,
-            $entity
-            ->setContentType('text/plain')
-            ->setEncoder($this->createEncoder())
-            ->setId('foo@bar')
-            ->setDescription('my description')
-            ->setMaxLineLength(998)
-            ->setBody('xx')
-            ->setBoundary('xyz')
-            ->setChildren([])
-            );
-    }
-
-    abstract protected function createEntity($headers, $encoder, $cache);
-
-    protected function createChild($level = null, $string = '', $stub = true)
-    {
-        $child = $this->getMockery('Swift_Mime_SimpleMimeEntity')->shouldIgnoreMissing();
-        if (isset($level)) {
-            $child->shouldReceive('getNestingLevel')
-                  ->zeroOrMoreTimes()
-                  ->andReturn($level);
-        }
-        $child->shouldReceive('toString')
-              ->zeroOrMoreTimes()
-              ->andReturn($string);
-
-        return $child;
-    }
-
-    protected function createEncoder($name = 'quoted-printable', $stub = true)
-    {
-        $encoder = $this->createMock('Swift_Mime_ContentEncoder');
-        $encoder->expects($this->any())
-                ->method('getName')
-                ->willReturn($name);
-        $encoder->expects($this->any())
-                ->method('encodeString')
-                ->willReturnCallback(function () {
-                    $args = \func_get_args();
-
-                    return array_shift($args);
-                });
-
-        return $encoder;
-    }
-
-    protected function createCache($stub = true)
-    {
-        return $this->getMockery('Swift_KeyCache')->shouldIgnoreMissing();
-    }
-
-    protected function createHeaderSet($headers = [], $stub = true)
-    {
-        $set = $this->getMockery('Swift_Mime_SimpleHeaderSet')->shouldIgnoreMissing();
-        $set->shouldReceive('get')
-            ->zeroOrMoreTimes()
-            ->andReturnUsing(function ($key) use ($headers) {
-                return $headers[$key];
-            });
-        $set->shouldReceive('has')
-            ->zeroOrMoreTimes()
-            ->andReturnUsing(function ($key) use ($headers) {
-                return \array_key_exists($key, $headers);
-            });
-
-        return $set;
-    }
-
-    protected function createHeader($name, $model = null, $params = [], $stub = true)
-    {
-        $header = $this->getMockery('Swift_Mime_Headers_ParameterizedHeader')->shouldIgnoreMissing();
-        $header->shouldReceive('getFieldName')
-               ->zeroOrMoreTimes()
-               ->andReturn($name);
-        $header->shouldReceive('getFieldBodyModel')
-               ->zeroOrMoreTimes()
-               ->andReturn($model);
-        $header->shouldReceive('getParameter')
-               ->zeroOrMoreTimes()
-               ->andReturnUsing(function ($key) use ($params) {
-                   return $params[$key];
-               });
-
-        return $header;
-    }
-
-    protected function createOutputStream($data = null, $stub = true)
-    {
-        $os = $this->getMockery('Swift_OutputByteStream');
-        if (isset($data)) {
-            $os->shouldReceive('read')
-               ->zeroOrMoreTimes()
-               ->andReturnUsing(function () use ($data) {
-                   static $first = true;
-                   if (!$first) {
-                       return false;
-                   }
-
-                   $first = false;
-
-                   return $data;
-               });
-            $os->shouldReceive('setReadPointer')
-              ->zeroOrMoreTimes();
-        }
-
-        return $os;
-    }
-
-    protected function createInputStream($stub = true)
-    {
-        return $this->createMock('Swift_InputByteStream');
-    }
-}
diff --git a/tests/unit/Swift/Mime/AbstractMimeEntityTestCase.php b/tests/unit/Swift/Mime/AbstractMimeEntityTestCase.php
new file mode 100644
index 0000000..c9466e1
--- /dev/null
+++ b/tests/unit/Swift/Mime/AbstractMimeEntityTestCase.php
@@ -0,0 +1,1092 @@
+<?php
+
+require_once \dirname(__DIR__, 3).'/fixtures/MimeEntityFixture.php';
+
+abstract class Swift_Mime_AbstractMimeEntityTestCase extends \SwiftMailerTestCase
+{
+    public function testGetHeadersReturnsHeaderSet()
+    {
+        $headers = $this->createHeaderSet();
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $this->assertSame($headers, $entity->getHeaders());
+    }
+
+    public function testContentTypeIsReturnedFromHeader()
+    {
+        $ctype = $this->createHeader('Content-Type', 'image/jpeg-test');
+        $headers = $this->createHeaderSet(['Content-Type' => $ctype]);
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $this->assertEquals('image/jpeg-test', $entity->getContentType());
+    }
+
+    public function testContentTypeIsSetInHeader()
+    {
+        $ctype = $this->createHeader('Content-Type', 'text/plain', [], false);
+        $headers = $this->createHeaderSet(['Content-Type' => $ctype]);
+
+        $ctype->shouldReceive('setFieldBodyModel')
+              ->once()
+              ->with('image/jpeg');
+        $ctype->shouldReceive('setFieldBodyModel')
+              ->zeroOrMoreTimes()
+              ->with(\Mockery::not('image/jpeg'));
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setContentType('image/jpeg');
+    }
+
+    public function testContentTypeHeaderIsAddedIfNoneSet()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('addParameterizedHeader')
+                ->once()
+                ->with('Content-Type', 'image/jpeg');
+        $headers->shouldReceive('addParameterizedHeader')
+                ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setContentType('image/jpeg');
+    }
+
+    public function testContentTypeCanBeSetViaSetBody()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('addParameterizedHeader')
+                ->once()
+                ->with('Content-Type', 'text/html');
+        $headers->shouldReceive('addParameterizedHeader')
+                ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setBody('<b>foo</b>', 'text/html');
+    }
+
+    public function testGetEncoderFromConstructor()
+    {
+        $encoder = $this->createEncoder('base64');
+        $entity = $this->createEntity($this->createHeaderSet(), $encoder,
+            $this->createCache()
+            );
+        $this->assertSame($encoder, $entity->getEncoder());
+    }
+
+    public function testSetAndGetEncoder()
+    {
+        $encoder = $this->createEncoder('base64');
+        $headers = $this->createHeaderSet();
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setEncoder($encoder);
+        $this->assertSame($encoder, $entity->getEncoder());
+    }
+
+    public function testSettingEncoderUpdatesTransferEncoding()
+    {
+        $encoder = $this->createEncoder('base64');
+        $encoding = $this->createHeader(
+            'Content-Transfer-Encoding', '8bit', [], false
+            );
+        $headers = $this->createHeaderSet([
+            'Content-Transfer-Encoding' => $encoding,
+            ]);
+        $encoding->shouldReceive('setFieldBodyModel')
+                 ->once()
+                 ->with('base64');
+        $encoding->shouldReceive('setFieldBodyModel')
+                 ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setEncoder($encoder);
+    }
+
+    public function testSettingEncoderAddsEncodingHeaderIfNonePresent()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('addTextHeader')
+                ->once()
+                ->with('Content-Transfer-Encoding', 'something');
+        $headers->shouldReceive('addTextHeader')
+                ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setEncoder($this->createEncoder('something'));
+    }
+
+    public function testIdIsReturnedFromHeader()
+    {
+        /* -- RFC 2045, 7.
+        In constructing a high-level user agent, it may be desirable to allow
+        one body to make reference to another.  Accordingly, bodies may be
+        labelled using the "Content-ID" header field, which is syntactically
+        identical to the "Message-ID" header field
+        */
+
+        $cid = $this->createHeader('Content-ID', 'zip@button');
+        $headers = $this->createHeaderSet(['Content-ID' => $cid]);
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $this->assertEquals('zip@button', $entity->getId());
+    }
+
+    public function testIdIsSetInHeader()
+    {
+        $cid = $this->createHeader('Content-ID', 'zip@button', [], false);
+        $headers = $this->createHeaderSet(['Content-ID' => $cid]);
+
+        $cid->shouldReceive('setFieldBodyModel')
+            ->once()
+            ->with('foo@bar');
+        $cid->shouldReceive('setFieldBodyModel')
+            ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setId('foo@bar');
+    }
+
+    public function testIdIsAutoGenerated()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $this->assertMatchesRegularExpression('/^.*?@.*?$/D', $entity->getId());
+    }
+
+    public function testGenerateIdCreatesNewId()
+    {
+        $headers = $this->createHeaderSet();
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $id1 = $entity->generateId();
+        $id2 = $entity->generateId();
+        $this->assertNotEquals($id1, $id2);
+    }
+
+    public function testGenerateIdSetsNewId()
+    {
+        $headers = $this->createHeaderSet();
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $id = $entity->generateId();
+        $this->assertEquals($id, $entity->getId());
+    }
+
+    public function testDescriptionIsReadFromHeader()
+    {
+        /* -- RFC 2045, 8.
+        The ability to associate some descriptive information with a given
+        body is often desirable.  For example, it may be useful to mark an
+        "image" body as "a picture of the Space Shuttle Endeavor."  Such text
+        may be placed in the Content-Description header field.  This header
+        field is always optional.
+        */
+
+        $desc = $this->createHeader('Content-Description', 'something');
+        $headers = $this->createHeaderSet(['Content-Description' => $desc]);
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $this->assertEquals('something', $entity->getDescription());
+    }
+
+    public function testDescriptionIsSetInHeader()
+    {
+        $desc = $this->createHeader('Content-Description', '', [], false);
+        $desc->shouldReceive('setFieldBodyModel')->once()->with('whatever');
+
+        $headers = $this->createHeaderSet(['Content-Description' => $desc]);
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setDescription('whatever');
+    }
+
+    public function testDescriptionHeaderIsAddedIfNotPresent()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('addTextHeader')
+                ->once()
+                ->with('Content-Description', 'whatever');
+        $headers->shouldReceive('addTextHeader')
+                ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setDescription('whatever');
+    }
+
+    public function testSetAndGetMaxLineLength()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setMaxLineLength(60);
+        $this->assertEquals(60, $entity->getMaxLineLength());
+    }
+
+    public function testEncoderIsUsedForStringGeneration()
+    {
+        $encoder = $this->createEncoder('base64', false);
+        $encoder->expects($this->once())
+                ->method('encodeString')
+                ->with('blah');
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $encoder, $this->createCache()
+            );
+        $entity->setBody('blah');
+        $entity->toString();
+    }
+
+    public function testMaxLineLengthIsProvidedWhenEncoding()
+    {
+        $encoder = $this->createEncoder('base64', false);
+        $encoder->expects($this->once())
+                ->method('encodeString')
+                ->with('blah', 0, 65);
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $encoder, $this->createCache()
+            );
+        $entity->setBody('blah');
+        $entity->setMaxLineLength(65);
+        $entity->toString();
+    }
+
+    public function testHeadersAppearInString()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->once()
+                ->andReturn(
+                    "Content-Type: text/plain; charset=utf-8\r\n".
+                    "X-MyHeader: foobar\r\n"
+                );
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $this->assertEquals(
+            "Content-Type: text/plain; charset=utf-8\r\n".
+            "X-MyHeader: foobar\r\n",
+            $entity->toString()
+            );
+    }
+
+    public function testSetAndGetBody()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setBody("blah\r\nblah!");
+        $this->assertEquals("blah\r\nblah!", $entity->getBody());
+    }
+
+    public function testBodyIsAppended()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->once()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setBody("blah\r\nblah!");
+        $this->assertEquals(
+            "Content-Type: text/plain; charset=utf-8\r\n".
+            "\r\n".
+            "blah\r\nblah!",
+            $entity->toString()
+            );
+    }
+
+    public function testGetBodyReturnsStringFromByteStream()
+    {
+        $os = $this->createOutputStream('byte stream string');
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setBody($os);
+        $this->assertEquals('byte stream string', $entity->getBody());
+    }
+
+    public function testByteStreamBodyIsAppended()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $os = $this->createOutputStream('streamed');
+        $headers->shouldReceive('toString')
+                ->once()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setBody($os);
+        $this->assertEquals(
+            "Content-Type: text/plain; charset=utf-8\r\n".
+            "\r\n".
+            'streamed',
+            $entity->toString()
+            );
+    }
+
+    public function testBoundaryCanBeRetrieved()
+    {
+        /* -- RFC 2046, 5.1.1.
+     boundary := 0*69<bchars> bcharsnospace
+
+     bchars := bcharsnospace / " "
+
+     bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
+                                            "+" / "_" / "," / "-" / "." /
+                                            "/" / ":" / "=" / "?"
+        */
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $this->assertMatchesRegularExpression(
+            '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D',
+            $entity->getBoundary()
+            );
+    }
+
+    public function testBoundaryNeverChanges()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $firstBoundary = $entity->getBoundary();
+        for ($i = 0; $i < 10; ++$i) {
+            $this->assertEquals($firstBoundary, $entity->getBoundary());
+        }
+    }
+
+    public function testBoundaryCanBeSet()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setBoundary('foobar');
+        $this->assertEquals('foobar', $entity->getBoundary());
+    }
+
+    public function testAddingChildrenGeneratesBoundaryInHeaders()
+    {
+        $child = $this->createChild();
+        $cType = $this->createHeader('Content-Type', 'text/plain', [], false);
+        $cType->shouldReceive('setParameter')
+              ->once()
+              ->with('boundary', \Mockery::any());
+        $cType->shouldReceive('setParameter')
+              ->zeroOrMoreTimes();
+
+        $entity = $this->createEntity($this->createHeaderSet([
+            'Content-Type' => $cType,
+            ]),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setChildren([$child]);
+    }
+
+    public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed()
+    {
+        for ($level = Swift_Mime_SimpleMimeEntity::LEVEL_MIXED;
+            $level > Swift_Mime_SimpleMimeEntity::LEVEL_TOP; $level /= 2) {
+            $child = $this->createChild($level);
+            $cType = $this->createHeader(
+                'Content-Type', 'text/plain', [], false
+                );
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->once()
+                  ->with('multipart/mixed');
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->zeroOrMoreTimes();
+
+            $entity = $this->createEntity($this->createHeaderSet([
+                'Content-Type' => $cType, ]),
+                $this->createEncoder(), $this->createCache()
+                );
+            $entity->setChildren([$child]);
+        }
+    }
+
+    public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative()
+    {
+        for ($level = Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE;
+            $level > Swift_Mime_SimpleMimeEntity::LEVEL_MIXED; $level /= 2) {
+            $child = $this->createChild($level);
+            $cType = $this->createHeader(
+                'Content-Type', 'text/plain', [], false
+                );
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->once()
+                  ->with('multipart/alternative');
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->zeroOrMoreTimes();
+
+            $entity = $this->createEntity($this->createHeaderSet([
+                'Content-Type' => $cType, ]),
+                $this->createEncoder(), $this->createCache()
+                );
+            $entity->setChildren([$child]);
+        }
+    }
+
+    public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated()
+    {
+        for ($level = Swift_Mime_SimpleMimeEntity::LEVEL_RELATED;
+            $level > Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE; $level /= 2) {
+            $child = $this->createChild($level);
+            $cType = $this->createHeader(
+                'Content-Type', 'text/plain', [], false
+                );
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->once()
+                  ->with('multipart/related');
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->zeroOrMoreTimes();
+
+            $entity = $this->createEntity($this->createHeaderSet([
+                'Content-Type' => $cType, ]),
+                $this->createEncoder(), $this->createCache()
+                );
+            $entity->setChildren([$child]);
+        }
+    }
+
+    public function testHighestLevelChildDeterminesContentType()
+    {
+        $combinations = [
+            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
+                Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+                Swift_Mime_SimpleMimeEntity::LEVEL_RELATED,
+                ],
+                'type' => 'multipart/mixed',
+                ],
+            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
+                Swift_Mime_SimpleMimeEntity::LEVEL_RELATED,
+                ],
+                'type' => 'multipart/mixed',
+                ],
+            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
+                Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+                ],
+                'type' => 'multipart/mixed',
+                ],
+            ['levels' => [Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+                Swift_Mime_SimpleMimeEntity::LEVEL_RELATED,
+                ],
+                'type' => 'multipart/alternative',
+                ],
+            ];
+
+        foreach ($combinations as $combination) {
+            $children = [];
+            foreach ($combination['levels'] as $level) {
+                $children[] = $this->createChild($level);
+            }
+
+            $cType = $this->createHeader(
+                'Content-Type', 'text/plain', [], false
+                );
+            $cType->shouldReceive('setFieldBodyModel')
+                  ->once()
+                  ->with($combination['type']);
+
+            $headerSet = $this->createHeaderSet(['Content-Type' => $cType]);
+            $headerSet->shouldReceive('newInstance')
+                      ->zeroOrMoreTimes()
+                      ->andReturnUsing(function () use ($headerSet) {
+                          return $headerSet;
+                      });
+            $entity = $this->createEntity($headerSet,
+                $this->createEncoder(), $this->createCache()
+                );
+            $entity->setChildren($children);
+        }
+    }
+
+    public function testChildrenAppearNestedInString()
+    {
+        /* -- RFC 2046, 5.1.1.
+     (excerpt too verbose to paste here)
+     */
+
+        $headers = $this->createHeaderSet([], false);
+
+        $child1 = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'foobar', 'text/plain'
+            );
+
+        $child2 = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/html\r\n".
+            "\r\n".
+            '<b>foobar</b>', 'text/html'
+            );
+
+        $headers->shouldReceive('toString')
+              ->zeroOrMoreTimes()
+              ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setBoundary('xxx');
+        $entity->setChildren([$child1, $child2]);
+
+        $this->assertEquals(
+            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
+            "\r\n".
+            "\r\n--xxx\r\n".
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            "foobar\r\n".
+            "\r\n--xxx\r\n".
+            "Content-Type: text/html\r\n".
+            "\r\n".
+            "<b>foobar</b>\r\n".
+            "\r\n--xxx--\r\n",
+            $entity->toString()
+            );
+    }
+
+    public function testMixingLevelsIsHierarchical()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $newHeaders = $this->createHeaderSet([], false);
+
+        $part = $this->createChild(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'foobar'
+            );
+
+        $attachment = $this->createChild(Swift_Mime_SimpleMimeEntity::LEVEL_MIXED,
+            "Content-Type: application/octet-stream\r\n".
+            "\r\n".
+            'data'
+            );
+
+        $headers->shouldReceive('toString')
+              ->zeroOrMoreTimes()
+              ->andReturn("Content-Type: multipart/mixed; boundary=\"xxx\"\r\n");
+        $headers->shouldReceive('newInstance')
+              ->zeroOrMoreTimes()
+              ->andReturn($newHeaders);
+        $newHeaders->shouldReceive('toString')
+              ->zeroOrMoreTimes()
+              ->andReturn("Content-Type: multipart/alternative; boundary=\"yyy\"\r\n");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setBoundary('xxx');
+        $entity->setChildren([$part, $attachment]);
+
+        $this->assertMatchesRegularExpression(
+            '~^'.
+            "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n".
+            "\r\n\r\n--xxx\r\n".
+            "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n".
+            "\r\n\r\n--(.*?)\r\n".
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'foobar'.
+            "\r\n\r\n--\\1--\r\n".
+            "\r\n\r\n--xxx\r\n".
+            "Content-Type: application/octet-stream\r\n".
+            "\r\n".
+            'data'.
+            "\r\n\r\n--xxx--\r\n".
+            '$~',
+            $entity->toString()
+            );
+    }
+
+    public function testSettingEncoderNotifiesChildren()
+    {
+        $child = $this->createChild(0, '', false);
+        $encoder = $this->createEncoder('base64');
+
+        $child->shouldReceive('encoderChanged')
+              ->once()
+              ->with($encoder);
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setChildren([$child]);
+        $entity->setEncoder($encoder);
+    }
+
+    public function testReceiptOfEncoderChangeNotifiesChildren()
+    {
+        $child = $this->createChild(0, '', false);
+        $encoder = $this->createEncoder('base64');
+
+        $child->shouldReceive('encoderChanged')
+              ->once()
+              ->with($encoder);
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setChildren([$child]);
+        $entity->encoderChanged($encoder);
+    }
+
+    public function testReceiptOfCharsetChangeNotifiesChildren()
+    {
+        $child = $this->createChild(0, '', false);
+        $child->shouldReceive('charsetChanged')
+              ->once()
+              ->with('windows-874');
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $entity->setChildren([$child]);
+        $entity->charsetChanged('windows-874');
+    }
+
+    public function testEntityIsWrittenToByteStream()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $is = $this->createInputStream(false);
+        $is->expects($this->atLeastOnce())
+           ->method('write');
+
+        $entity->toByteStream($is);
+    }
+
+    public function testEntityHeadersAreComittedToByteStream()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+        $is = $this->createInputStream(false);
+        $is->expects($this->atLeastOnce())
+           ->method('write');
+        $is->expects($this->atLeastOnce())
+           ->method('commit');
+
+        $entity->toByteStream($is);
+    }
+
+    public function testOrderingTextBeforeHtml()
+    {
+        $htmlChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/html\r\n".
+            "\r\n".
+            'HTML PART',
+            'text/html'
+            );
+        $textChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'TEXT PART',
+            'text/plain'
+            );
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->zeroOrMoreTimes()
+                ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+            );
+        $entity->setBoundary('xxx');
+        $entity->setChildren([$htmlChild, $textChild]);
+
+        $this->assertEquals(
+            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
+            "\r\n\r\n--xxx\r\n".
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'TEXT PART'.
+            "\r\n\r\n--xxx\r\n".
+            "Content-Type: text/html\r\n".
+            "\r\n".
+            'HTML PART'.
+            "\r\n\r\n--xxx--\r\n",
+            $entity->toString()
+            );
+    }
+
+    public function testOrderingEqualContentTypesMaintainsOriginalOrdering()
+    {
+        $firstChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'PART 1',
+            'text/plain'
+        );
+        $secondChild = new MimeEntityFixture(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE,
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'PART 2',
+            'text/plain'
+        );
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+            ->zeroOrMoreTimes()
+            ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $this->createCache()
+        );
+        $entity->setBoundary('xxx');
+        $entity->setChildren([$firstChild, $secondChild]);
+
+        $this->assertEquals(
+            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
+            "\r\n\r\n--xxx\r\n".
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'PART 1'.
+            "\r\n\r\n--xxx\r\n".
+            "Content-Type: text/plain\r\n".
+            "\r\n".
+            'PART 2'.
+            "\r\n\r\n--xxx--\r\n",
+            $entity->toString()
+        );
+    }
+
+    public function testUnsettingChildrenRestoresContentType()
+    {
+        $cType = $this->createHeader('Content-Type', 'text/plain', [], false);
+        $child = $this->createChild(Swift_Mime_SimpleMimeEntity::LEVEL_ALTERNATIVE);
+
+        $cType->shouldReceive('setFieldBodyModel')
+              ->twice()
+              ->with('image/jpeg');
+        $cType->shouldReceive('setFieldBodyModel')
+              ->once()
+              ->with('multipart/alternative');
+        $cType->shouldReceive('setFieldBodyModel')
+              ->zeroOrMoreTimes()
+              ->with(\Mockery::not('multipart/alternative', 'image/jpeg'));
+
+        $entity = $this->createEntity($this->createHeaderSet([
+            'Content-Type' => $cType,
+            ]),
+            $this->createEncoder(), $this->createCache()
+            );
+
+        $entity->setContentType('image/jpeg');
+        $entity->setChildren([$child]);
+        $entity->setChildren([]);
+    }
+
+    public function testBodyIsReadFromCacheWhenUsingToStringIfPresent()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->zeroOrMoreTimes()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $cache = $this->createCache(false);
+        $cache->shouldReceive('hasKey')
+              ->once()
+              ->with(\Mockery::any(), 'body')
+              ->andReturn(true);
+        $cache->shouldReceive('getString')
+              ->once()
+              ->with(\Mockery::any(), 'body')
+              ->andReturn("\r\ncache\r\ncache!");
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $cache
+            );
+
+        $entity->setBody("blah\r\nblah!");
+        $this->assertEquals(
+            "Content-Type: text/plain; charset=utf-8\r\n".
+            "\r\n".
+            "cache\r\ncache!",
+            $entity->toString()
+            );
+    }
+
+    public function testBodyIsAddedToCacheWhenUsingToString()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->zeroOrMoreTimes()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $cache = $this->createCache(false);
+        $cache->shouldReceive('hasKey')
+              ->once()
+              ->with(\Mockery::any(), 'body')
+              ->andReturn(false);
+        $cache->shouldReceive('setString')
+              ->once()
+              ->with(\Mockery::any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE);
+
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $cache
+            );
+
+        $entity->setBody("blah\r\nblah!");
+        $entity->toString();
+    }
+
+    public function testBodyIsClearedFromCacheIfNewBodySet()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->zeroOrMoreTimes()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $cache = $this->createCache(false);
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $cache
+            );
+
+        $entity->setBody("blah\r\nblah!");
+        $entity->toString();
+
+        // We set the expectation at this point because we only care what happens when calling setBody()
+        $cache->shouldReceive('clearKey')
+              ->once()
+              ->with(\Mockery::any(), 'body');
+
+        $entity->setBody("new\r\nnew!");
+    }
+
+    public function testBodyIsNotClearedFromCacheIfSameBodySet()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->zeroOrMoreTimes()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $cache = $this->createCache(false);
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $cache
+            );
+
+        $entity->setBody("blah\r\nblah!");
+        $entity->toString();
+
+        // We set the expectation at this point because we only care what happens when calling setBody()
+        $cache->shouldReceive('clearKey')
+              ->never();
+
+        $entity->setBody("blah\r\nblah!");
+    }
+
+    public function testBodyIsClearedFromCacheIfNewEncoderSet()
+    {
+        $headers = $this->createHeaderSet([], false);
+        $headers->shouldReceive('toString')
+                ->zeroOrMoreTimes()
+                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");
+
+        $cache = $this->createCache(false);
+        $otherEncoder = $this->createEncoder();
+        $entity = $this->createEntity($headers, $this->createEncoder(),
+            $cache
+            );
+
+        $entity->setBody("blah\r\nblah!");
+        $entity->toString();
+
+        // We set the expectation at this point because we only care what happens when calling setEncoder()
+        $cache->shouldReceive('clearKey')
+              ->once()
+              ->with(\Mockery::any(), 'body');
+
+        $entity->setEncoder($otherEncoder);
+    }
+
+    public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent()
+    {
+        $is = $this->createInputStream();
+        $cache = $this->createCache(false);
+        $cache->shouldReceive('hasKey')
+              ->once()
+              ->with(\Mockery::any(), 'body')
+              ->andReturn(true);
+        $cache->shouldReceive('exportToByteStream')
+              ->once()
+              ->with(\Mockery::any(), 'body', $is);
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $cache
+            );
+        $entity->setBody('foo');
+
+        $entity->toByteStream($is);
+    }
+
+    public function testBodyIsAddedToCacheWhenUsingToByteStream()
+    {
+        $is = $this->createInputStream();
+        $cache = $this->createCache(false);
+        $cache->shouldReceive('hasKey')
+              ->once()
+              ->with(\Mockery::any(), 'body')
+              ->andReturn(false);
+        $cache->shouldReceive('getInputByteStream')
+              ->once()
+              ->with(\Mockery::any(), 'body');
+
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $cache
+            );
+        $entity->setBody('foo');
+
+        $entity->toByteStream($is);
+    }
+
+    public function testFluidInterface()
+    {
+        $entity = $this->createEntity($this->createHeaderSet(),
+            $this->createEncoder(), $this->createCache()
+            );
+
+        $this->assertSame($entity,
+            $entity
+            ->setContentType('text/plain')
+            ->setEncoder($this->createEncoder())
+            ->setId('foo@bar')
+            ->setDescription('my description')
+            ->setMaxLineLength(998)
+            ->setBody('xx')
+            ->setBoundary('xyz')
+            ->setChildren([])
+            );
+    }
+
+    abstract protected function createEntity($headers, $encoder, $cache);
+
+    protected function createChild($level = null, $string = '', $stub = true)
+    {
+        $child = $this->getMockery('Swift_Mime_SimpleMimeEntity')->shouldIgnoreMissing();
+        if (isset($level)) {
+            $child->shouldReceive('getNestingLevel')
+                  ->zeroOrMoreTimes()
+                  ->andReturn($level);
+        }
+        $child->shouldReceive('toString')
+              ->zeroOrMoreTimes()
+              ->andReturn($string);
+
+        return $child;
+    }
+
+    protected function createEncoder($name = 'quoted-printable', $stub = true)
+    {
+        $encoder = $this->createMock('Swift_Mime_ContentEncoder');
+        $encoder->expects($this->any())
+                ->method('getName')
+                ->willReturn($name);
+        $encoder->expects($this->any())
+                ->method('encodeString')
+                ->willReturnCallback(function () {
+                    $args = \func_get_args();
+
+                    return array_shift($args);
+                });
+
+        return $encoder;
+    }
+
+    protected function createCache($stub = true)
+    {
+        return $this->getMockery('Swift_KeyCache')->shouldIgnoreMissing();
+    }
+
+    protected function createHeaderSet($headers = [], $stub = true)
+    {
+        $set = $this->getMockery('Swift_Mime_SimpleHeaderSet')->shouldIgnoreMissing();
+        $set->shouldReceive('get')
+            ->zeroOrMoreTimes()
+            ->andReturnUsing(function ($key) use ($headers) {
+                return $headers[$key];
+            });
+        $set->shouldReceive('has')
+            ->zeroOrMoreTimes()
+            ->andReturnUsing(function ($key) use ($headers) {
+                return \array_key_exists($key, $headers);
+            });
+
+        return $set;
+    }
+
+    protected function createHeader($name, $model = null, $params = [], $stub = true)
+    {
+        $header = $this->getMockery('Swift_Mime_Headers_ParameterizedHeader')->shouldIgnoreMissing();
+        $header->shouldReceive('getFieldName')
+               ->zeroOrMoreTimes()
+               ->andReturn($name);
+        $header->shouldReceive('getFieldBodyModel')
+               ->zeroOrMoreTimes()
+               ->andReturn($model);
+        $header->shouldReceive('getParameter')
+               ->zeroOrMoreTimes()
+               ->andReturnUsing(function ($key) use ($params) {
+                   return $params[$key];
+               });
+
+        return $header;
+    }
+
+    protected function createOutputStream($data = null, $stub = true)
+    {
+        $os = $this->getMockery('Swift_OutputByteStream');
+        if (isset($data)) {
+            $os->shouldReceive('read')
+               ->zeroOrMoreTimes()
+               ->andReturnUsing(function () use ($data) {
+                   static $first = true;
+                   if (!$first) {
+                       return false;
+                   }
+
+                   $first = false;
+
+                   return $data;
+               });
+            $os->shouldReceive('setReadPointer')
+              ->zeroOrMoreTimes();
+        }
+
+        return $os;
+    }
+
+    protected function createInputStream($stub = true)
+    {
+        return $this->createMock('Swift_InputByteStream');
+    }
+}
diff --git a/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php b/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php
deleted file mode 100644
index 896171d..0000000
--- a/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php
+++ /dev/null
@@ -1,558 +0,0 @@
-<?php
-
-require_once __DIR__.'/AbstractSmtpTest.php';
-
-abstract class Swift_Transport_AbstractSmtpEventSupportTest extends Swift_Transport_AbstractSmtpTest
-{
-    public function testRegisterPluginLoadsPluginInEventDispatcher()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $listener = $this->getMockery('Swift_Events_EventListener');
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $dispatcher->shouldReceive('bindEventListener')
-                   ->once()
-                   ->with($listener);
-
-        $smtp->registerPlugin($listener);
-    }
-
-    public function testSendingDispatchesBeforeSendEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $message = $this->createMessage();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->once()
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'beforeSendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(1, $smtp->send($message));
-    }
-
-    public function testSendingDispatchesSendEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $message = $this->createMessage();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->once()
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'sendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(1, $smtp->send($message));
-    }
-
-    public function testSendEventCapturesFailures()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn("500 Not now\r\n");
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->zeroOrMoreTimes()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'sendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-        $evt->shouldReceive('setFailedRecipients')
-            ->once()
-            ->with(['mark@swiftmailer.org']);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(0, $smtp->send($message));
-    }
-
-    public function testSendEventHasResultFailedIfAllFailures()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn("500 Not now\r\n");
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->zeroOrMoreTimes()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'sendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-        $evt->shouldReceive('setResult')
-            ->once()
-            ->with(Swift_Events_SendEvent::RESULT_FAILED);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(0, $smtp->send($message));
-    }
-
-    public function testSendEventHasResultTentativeIfSomeFailures()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn([
-                    'mark@swiftmailer.org' => 'Mark',
-                    'chris@site.tld' => 'Chris',
-                ]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn("500 Not now\r\n");
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->zeroOrMoreTimes()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'sendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-        $evt->shouldReceive('setResult')
-            ->once()
-            ->with(Swift_Events_SendEvent::RESULT_TENTATIVE);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(1, $smtp->send($message));
-    }
-
-    public function testSendEventHasResultSuccessIfNoFailures()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn([
-                    'mark@swiftmailer.org' => 'Mark',
-                    'chris@site.tld' => 'Chris',
-                ]);
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->zeroOrMoreTimes()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'sendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-        $evt->shouldReceive('setResult')
-            ->once()
-            ->with(Swift_Events_SendEvent::RESULT_SUCCESS);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(2, $smtp->send($message));
-    }
-
-    public function testCancellingEventBubbleBeforeSendStopsEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['chris@swiftmailer.org' => null]);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
-        $dispatcher->shouldReceive('createSendEvent')
-                   ->zeroOrMoreTimes()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'beforeSendPerformed');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->atLeast()->once()
-            ->andReturn(true);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(0, $smtp->send($message));
-    }
-
-    public function testStartingTransportDispatchesTransportChangeEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createTransportChangeEvent')
-                   ->atLeast()->once()
-                   ->with($smtp)
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'transportStarted');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->atLeast()->once()
-            ->andReturn(false);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-    }
-
-    public function testStartingTransportDispatchesBeforeTransportChangeEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createTransportChangeEvent')
-                   ->atLeast()->once()
-                   ->with($smtp)
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'beforeTransportStarted');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->atLeast()->once()
-            ->andReturn(false);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-    }
-
-    public function testCancellingBubbleBeforeTransportStartStopsEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createTransportChangeEvent')
-                   ->atLeast()->once()
-                   ->with($smtp)
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'beforeTransportStarted');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->atLeast()->once()
-            ->andReturn(true);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-
-        $this->assertFalse($smtp->isStarted(),
-            '%s: Transport should not be started since event bubble was cancelled'
-        );
-    }
-
-    public function testStoppingTransportDispatchesTransportChangeEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createTransportChangeEvent')
-                   ->atLeast()->once()
-                   ->with($smtp)
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'transportStopped');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->stop();
-    }
-
-    public function testStoppingTransportDispatchesBeforeTransportChangeEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createTransportChangeEvent')
-                   ->atLeast()->once()
-                   ->with($smtp)
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'beforeTransportStopped');
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->stop();
-    }
-
-    public function testCancellingBubbleBeforeTransportStoppedStopsEvent()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $hasRun = false;
-        $dispatcher->shouldReceive('createTransportChangeEvent')
-                   ->atLeast()->once()
-                   ->with($smtp)
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'beforeTransportStopped')
-                   ->andReturnUsing(function () use (&$hasRun) {
-                       $hasRun = true;
-                   });
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->zeroOrMoreTimes();
-        $evt->shouldReceive('bubbleCancelled')
-            ->zeroOrMoreTimes()
-            ->andReturnUsing(function () use (&$hasRun) {
-                return $hasRun;
-            });
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->stop();
-
-        $this->assertTrue($smtp->isStarted(),
-            '%s: Transport should not be stopped since event bubble was cancelled'
-        );
-    }
-
-    public function testResponseEventsAreGenerated()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_ResponseEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createResponseEvent')
-                   ->atLeast()->once()
-                   ->with($smtp, \Mockery::any(), \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->atLeast()->once()
-                   ->with($evt, 'responseReceived');
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-    }
-
-    public function testCommandEventsAreGenerated()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_CommandEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $dispatcher->shouldReceive('createCommandEvent')
-                   ->once()
-                   ->with($smtp, \Mockery::any(), \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'commandSent');
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-    }
-
-    public function testExceptionsCauseExceptionEvents()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $buf->shouldReceive('readLine')
-            ->atLeast()->once()
-            ->andReturn("503 I'm sleepy, go away!\r\n");
-        $dispatcher->shouldReceive('createTransportExceptionEvent')
-                   ->zeroOrMoreTimes()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->once()
-                   ->with($evt, 'exceptionThrown');
-        $evt->shouldReceive('bubbleCancelled')
-            ->atLeast()->once()
-            ->andReturn(false);
-
-        try {
-            $smtp->start();
-            $this->fail('TransportException should be thrown on invalid response');
-        } catch (Swift_TransportException $e) {
-        }
-    }
-
-    public function testExceptionBubblesCanBeCancelled()
-    {
-        $buf = $this->getBuffer();
-        $dispatcher = $this->createEventDispatcher(false);
-        $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
-        $smtp = $this->getTransport($buf, $dispatcher);
-
-        $buf->shouldReceive('readLine')
-            ->atLeast()->once()
-            ->andReturn("503 I'm sleepy, go away!\r\n");
-        $dispatcher->shouldReceive('createTransportExceptionEvent')
-                   ->twice()
-                   ->with($smtp, \Mockery::any())
-                   ->andReturn($evt);
-        $dispatcher->shouldReceive('dispatchEvent')
-                   ->twice()
-                   ->with($evt, 'exceptionThrown');
-        $evt->shouldReceive('bubbleCancelled')
-            ->atLeast()->once()
-            ->andReturn(true);
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-    }
-
-    protected function createEventDispatcher($stub = true)
-    {
-        return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing();
-    }
-}
diff --git a/tests/unit/Swift/Transport/AbstractSmtpEventSupportTestCase.php b/tests/unit/Swift/Transport/AbstractSmtpEventSupportTestCase.php
new file mode 100644
index 0000000..384cc01
--- /dev/null
+++ b/tests/unit/Swift/Transport/AbstractSmtpEventSupportTestCase.php
@@ -0,0 +1,558 @@
+<?php
+
+require_once __DIR__.'/AbstractSmtpTest.php';
+
+abstract class Swift_Transport_AbstractSmtpEventSupportTestCase extends Swift_Transport_AbstractSmtpTest
+{
+    public function testRegisterPluginLoadsPluginInEventDispatcher()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $listener = $this->getMockery('Swift_Events_EventListener');
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $dispatcher->shouldReceive('bindEventListener')
+                   ->once()
+                   ->with($listener);
+
+        $smtp->registerPlugin($listener);
+    }
+
+    public function testSendingDispatchesBeforeSendEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $message = $this->createMessage();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->once()
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'beforeSendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(1, $smtp->send($message));
+    }
+
+    public function testSendingDispatchesSendEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $message = $this->createMessage();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->once()
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'sendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(1, $smtp->send($message));
+    }
+
+    public function testSendEventCapturesFailures()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn("500 Not now\r\n");
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->zeroOrMoreTimes()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'sendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+        $evt->shouldReceive('setFailedRecipients')
+            ->once()
+            ->with(['mark@swiftmailer.org']);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(0, $smtp->send($message));
+    }
+
+    public function testSendEventHasResultFailedIfAllFailures()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn("500 Not now\r\n");
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->zeroOrMoreTimes()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'sendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+        $evt->shouldReceive('setResult')
+            ->once()
+            ->with(Swift_Events_SendEvent::RESULT_FAILED);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(0, $smtp->send($message));
+    }
+
+    public function testSendEventHasResultTentativeIfSomeFailures()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn([
+                    'mark@swiftmailer.org' => 'Mark',
+                    'chris@site.tld' => 'Chris',
+                ]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn("500 Not now\r\n");
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->zeroOrMoreTimes()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'sendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+        $evt->shouldReceive('setResult')
+            ->once()
+            ->with(Swift_Events_SendEvent::RESULT_TENTATIVE);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(1, $smtp->send($message));
+    }
+
+    public function testSendEventHasResultSuccessIfNoFailures()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn([
+                    'mark@swiftmailer.org' => 'Mark',
+                    'chris@site.tld' => 'Chris',
+                ]);
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->zeroOrMoreTimes()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'sendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+        $evt->shouldReceive('setResult')
+            ->once()
+            ->with(Swift_Events_SendEvent::RESULT_SUCCESS);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(2, $smtp->send($message));
+    }
+
+    public function testCancellingEventBubbleBeforeSendStopsEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['chris@swiftmailer.org' => null]);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['mark@swiftmailer.org' => 'Mark']);
+        $dispatcher->shouldReceive('createSendEvent')
+                   ->zeroOrMoreTimes()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'beforeSendPerformed');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->atLeast()->once()
+            ->andReturn(true);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(0, $smtp->send($message));
+    }
+
+    public function testStartingTransportDispatchesTransportChangeEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createTransportChangeEvent')
+                   ->atLeast()->once()
+                   ->with($smtp)
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'transportStarted');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->atLeast()->once()
+            ->andReturn(false);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+    }
+
+    public function testStartingTransportDispatchesBeforeTransportChangeEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createTransportChangeEvent')
+                   ->atLeast()->once()
+                   ->with($smtp)
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'beforeTransportStarted');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->atLeast()->once()
+            ->andReturn(false);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+    }
+
+    public function testCancellingBubbleBeforeTransportStartStopsEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createTransportChangeEvent')
+                   ->atLeast()->once()
+                   ->with($smtp)
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'beforeTransportStarted');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->atLeast()->once()
+            ->andReturn(true);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+
+        $this->assertFalse($smtp->isStarted(),
+            '%s: Transport should not be started since event bubble was cancelled'
+        );
+    }
+
+    public function testStoppingTransportDispatchesTransportChangeEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createTransportChangeEvent')
+                   ->atLeast()->once()
+                   ->with($smtp)
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'transportStopped');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->stop();
+    }
+
+    public function testStoppingTransportDispatchesBeforeTransportChangeEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createTransportChangeEvent')
+                   ->atLeast()->once()
+                   ->with($smtp)
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'beforeTransportStopped');
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->stop();
+    }
+
+    public function testCancellingBubbleBeforeTransportStoppedStopsEvent()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $hasRun = false;
+        $dispatcher->shouldReceive('createTransportChangeEvent')
+                   ->atLeast()->once()
+                   ->with($smtp)
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'beforeTransportStopped')
+                   ->andReturnUsing(function () use (&$hasRun) {
+                       $hasRun = true;
+                   });
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->zeroOrMoreTimes();
+        $evt->shouldReceive('bubbleCancelled')
+            ->zeroOrMoreTimes()
+            ->andReturnUsing(function () use (&$hasRun) {
+                return $hasRun;
+            });
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->stop();
+
+        $this->assertTrue($smtp->isStarted(),
+            '%s: Transport should not be stopped since event bubble was cancelled'
+        );
+    }
+
+    public function testResponseEventsAreGenerated()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_ResponseEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createResponseEvent')
+                   ->atLeast()->once()
+                   ->with($smtp, \Mockery::any(), \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->atLeast()->once()
+                   ->with($evt, 'responseReceived');
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+    }
+
+    public function testCommandEventsAreGenerated()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_CommandEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $dispatcher->shouldReceive('createCommandEvent')
+                   ->once()
+                   ->with($smtp, \Mockery::any(), \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'commandSent');
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+    }
+
+    public function testExceptionsCauseExceptionEvents()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $buf->shouldReceive('readLine')
+            ->atLeast()->once()
+            ->andReturn("503 I'm sleepy, go away!\r\n");
+        $dispatcher->shouldReceive('createTransportExceptionEvent')
+                   ->zeroOrMoreTimes()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->once()
+                   ->with($evt, 'exceptionThrown');
+        $evt->shouldReceive('bubbleCancelled')
+            ->atLeast()->once()
+            ->andReturn(false);
+
+        try {
+            $smtp->start();
+            $this->fail('TransportException should be thrown on invalid response');
+        } catch (Swift_TransportException $e) {
+        }
+    }
+
+    public function testExceptionBubblesCanBeCancelled()
+    {
+        $buf = $this->getBuffer();
+        $dispatcher = $this->createEventDispatcher(false);
+        $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
+        $smtp = $this->getTransport($buf, $dispatcher);
+
+        $buf->shouldReceive('readLine')
+            ->atLeast()->once()
+            ->andReturn("503 I'm sleepy, go away!\r\n");
+        $dispatcher->shouldReceive('createTransportExceptionEvent')
+                   ->twice()
+                   ->with($smtp, \Mockery::any())
+                   ->andReturn($evt);
+        $dispatcher->shouldReceive('dispatchEvent')
+                   ->twice()
+                   ->with($evt, 'exceptionThrown');
+        $evt->shouldReceive('bubbleCancelled')
+            ->atLeast()->once()
+            ->andReturn(true);
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+    }
+
+    protected function createEventDispatcher($stub = true)
+    {
+        return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing();
+    }
+}
diff --git a/tests/unit/Swift/Transport/AbstractSmtpTest.php b/tests/unit/Swift/Transport/AbstractSmtpTest.php
deleted file mode 100644
index dc9f05e..0000000
--- a/tests/unit/Swift/Transport/AbstractSmtpTest.php
+++ /dev/null
@@ -1,1311 +0,0 @@
-<?php
-
-abstract class Swift_Transport_AbstractSmtpTest extends \SwiftMailerTestCase
-{
-    abstract protected function getTransport($buf);
-
-    public function testStartAccepts220ServiceGreeting()
-    {
-        /* -- RFC 2821, 4.2.
-
-     Greeting = "220 " Domain [ SP text ] CRLF
-
-     -- RFC 2822, 4.3.2.
-
-     CONNECTION ESTABLISHMENT
-         S: 220
-         E: 554
-        */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("220 some.server.tld bleh\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
-            $smtp->start();
-            $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection');
-        } catch (Exception $e) {
-            $this->fail('220 is a valid SMTP greeting and should be accepted');
-        }
-    }
-
-    public function testBadGreetingCausesException()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("554 I'm busy\r\n");
-        $this->finishBuffer($buf);
-        try {
-            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
-            $smtp->start();
-            $this->fail('554 greeting indicates an error and should cause an exception');
-        } catch (Swift_TransportException $e) {
-            $this->assertFalse($smtp->isStarted(), '%s: start() should have failed');
-        }
-    }
-
-    public function testStartSendsHeloToInitiate()
-    {
-        /* -- RFC 2821, 3.2.
-
-            3.2 Client Initiation
-
-         Once the server has sent the welcoming message and the client has
-         received it, the client normally sends the EHLO command to the
-         server, indicating the client's identity.  In addition to opening the
-         session, use of EHLO indicates that the client is able to process
-         service extensions and requests that the server provide a list of the
-         extensions it supports.  Older SMTP systems which are unable to
-         support service extensions and contemporary clients which do not
-         require service extensions in the mail session being initiated, MAY
-         use HELO instead of EHLO.  Servers MUST NOT return the extended
-         EHLO-style response to a HELO command.  For a particular connection
-         attempt, if the server returns a "command not recognized" response to
-         EHLO, the client SHOULD be able to fall back and send HELO.
-
-         In the EHLO command the host sending the command identifies itself;
-         the command may be interpreted as saying "Hello, I am <domain>" (and,
-         in the case of EHLO, "and I support service extension requests").
-
-       -- RFC 2281, 4.1.1.1.
-
-       ehlo            = "EHLO" SP Domain CRLF
-       helo            = "HELO" SP Domain CRLF
-
-       -- RFC 2821, 4.3.2.
-
-       EHLO or HELO
-           S: 250
-           E: 504, 550
-
-     */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("220 some.server.tld bleh\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with(Mockery::pattern('~^HELO example.org\r\n$~D'))
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 ServerName'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-        } catch (Exception $e) {
-            $this->fail('Starting SMTP should send HELO and accept 250 response');
-        }
-    }
-
-    public function testInvalidHeloResponseCausesException()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("220 some.server.tld bleh\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with(Mockery::pattern('~^HELO example.org\r\n$~D'))
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('504 WTF'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
-            $smtp->start();
-            $this->fail('Non 250 HELO response should raise Exception');
-        } catch (Swift_TransportException $e) {
-            $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed');
-        }
-    }
-
-    public function testDomainNameIsPlacedInHelo()
-    {
-        /* -- RFC 2821, 4.1.4.
-
-       The SMTP client MUST, if possible, ensure that the domain parameter
-       to the EHLO command is a valid principal host name (not a CNAME or MX
-       name) for its host.  If this is not possible (e.g., when the client's
-       address is dynamically assigned and the client does not have an
-       obvious name), an address literal SHOULD be substituted for the
-       domain name and supplemental information provided that will assist in
-       identifying the client.
-        */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("220 some.server.tld bleh\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("HELO mydomain.com\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 ServerName'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->setLocalDomain('mydomain.com');
-        $smtp->start();
-    }
-
-    public function testSuccessfulMailCommand()
-    {
-        /* -- RFC 2821, 3.3.
-
-        There are three steps to SMTP mail transactions.  The transaction
-        starts with a MAIL command which gives the sender identification.
-
-        .....
-
-        The first step in the procedure is the MAIL command.
-
-            MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
-
-        -- RFC 2821, 4.1.1.2.
-
-        Syntax:
-
-            "MAIL FROM:" ("<>" / Reverse-Path)
-                       [SP Mail-parameters] CRLF
-        -- RFC 2821, 4.1.2.
-
-        Reverse-path = Path
-            Forward-path = Path
-            Path = "<" [ A-d-l ":" ] Mailbox ">"
-            A-d-l = At-domain *( "," A-d-l )
-                        ; Note that this form, the so-called "source route",
-                        ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be
-                        ; ignored.
-            At-domain = "@" domain
-
-        -- RFC 2821, 4.3.2.
-
-        MAIL
-            S: 250
-            E: 552, 451, 452, 550, 553, 503
-        */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<me@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn("250 OK\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-        } catch (Exception $e) {
-            $this->fail('MAIL FROM should accept a 250 response');
-        }
-    }
-
-    public function testInvalidResponseCodeFromMailCausesException()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<me@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('553 Bad'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-            $this->fail('MAIL FROM should accept a 250 response');
-        } catch (Swift_TransportException $e) {
-        }
-    }
-
-    public function testSenderIsPreferredOverFrom()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getSender')
-                ->once()
-                ->andReturn(['another@domain.com' => 'Someone']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<another@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testReturnPathIsPreferredOverSender()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getSender')
-                ->once()
-                ->andReturn(['another@domain.com' => 'Someone']);
-        $message->shouldReceive('getReturnPath')
-                ->once()
-                ->andReturn('more@domain.com');
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<more@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testSuccessfulRcptCommandWith250Response()
-    {
-        /* -- RFC 2821, 3.3.
-
-     The second step in the procedure is the RCPT command.
-
-            RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
-
-     The first or only argument to this command includes a forward-path
-     (normally a mailbox and domain, always surrounded by "<" and ">"
-     brackets) identifying one recipient.  If accepted, the SMTP server
-     returns a 250 OK reply and stores the forward-path.  If the recipient
-     is known not to be a deliverable address, the SMTP server returns a
-     550 reply, typically with a string such as "no such user - " and the
-     mailbox name (other circumstances and reply codes are possible).
-     This step of the procedure can be repeated any number of times.
-
-        -- RFC 2821, 4.1.1.3.
-
-        This command is used to identify an individual recipient of the mail
-        data; multiple recipients are specified by multiple use of this
-        command.  The argument field contains a forward-path and may contain
-        optional parameters.
-
-        The forward-path normally consists of the required destination
-        mailbox.  Sending systems SHOULD not generate the optional list of
-        hosts known as a source route.
-
-        .......
-
-        "RCPT TO:" ("<Postmaster@" domain ">" / "<Postmaster>" / Forward-Path)
-                                        [SP Rcpt-parameters] CRLF
-
-        -- RFC 2821, 4.2.2.
-
-            250 Requested mail action okay, completed
-            251 User not local; will forward to <forward-path>
-         (See section 3.4)
-            252 Cannot VRFY user, but will accept message and attempt
-                    delivery
-
-        -- RFC 2821, 4.3.2.
-
-        RCPT
-            S: 250, 251 (but see section 3.4 for discussion of 251 and 551)
-            E: 550, 551, 552, 553, 450, 451, 452, 503, 550
-        */
-
-        //We'll treat 252 as accepted since it isn't really a failure
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<me@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-        } catch (Exception $e) {
-            $this->fail('RCPT TO should accept a 250 response');
-        }
-    }
-
-    public function testUtf8AddressWithIdnEncoder()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@dömain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bär' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<me@xn--dmain-jua.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@xn--br-via>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testUtf8AddressWithUtf8Encoder()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf, null, new Swift_AddressEncoder_Utf8AddressEncoder());
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['më@dömain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['föö@bär' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<më@dömain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<föö@bär>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testNonEncodableSenderCausesException()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['më@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-            $this->fail('më@domain.com cannot be encoded (not observed)');
-        } catch (Swift_AddressEncoderException $e) {
-            $this->assertEquals('më@domain.com', $e->getAddress());
-        }
-    }
-
-    public function testMailFromCommandIsOnlySentOncePerMessage()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<me@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->never()
-            ->with("MAIL FROM:<me@domain.com>\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testMultipleRecipientsSendsMultipleRcpt()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn([
-                    'foo@bar' => null,
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                    'tëst@domain' => 'Test user',
-                ]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<zip@button>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<test@domain>\r\n")
-            ->andReturn(3);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(3)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testCcRecipientsSendsMultipleRcpt()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $message->shouldReceive('getCc')
-                ->once()
-                ->andReturn([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<zip@button>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<test@domain>\r\n")
-            ->andReturn(3);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(3)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testSendReturnsNumberOfSuccessfulRecipients()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $message->shouldReceive('getCc')
-                ->once()
-                ->andReturn([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<zip@button>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('501 Nobody here'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<test@domain>\r\n")
-            ->andReturn(3);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(3)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(2, $smtp->send($message),
-            '%s: 1 of 3 recipients failed so 2 should be returned'
-            );
-    }
-
-    public function testRsetIsSentIfNoSuccessfulRecipients()
-    {
-        /* --RFC 2821, 4.1.1.5.
-
-        This command specifies that the current mail transaction will be
-        aborted.  Any stored sender, recipients, and mail data MUST be
-        discarded, and all buffers and state tables cleared.  The receiver
-        MUST send a "250 OK" reply to a RSET command with no arguments.  A
-        reset command may be issued by the client at any time.
-
-        -- RFC 2821, 4.3.2.
-
-        RSET
-            S: 250
-        */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('503 Bad'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RSET\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(0, $smtp->send($message),
-            '%s: 1 of 1 recipients failed so 0 should be returned'
-            );
-    }
-
-    public function testSuccessfulDataCommand()
-    {
-        /* -- RFC 2821, 3.3.
-
-        The third step in the procedure is the DATA command (or some
-        alternative specified in a service extension).
-
-                    DATA <CRLF>
-
-        If accepted, the SMTP server returns a 354 Intermediate reply and
-        considers all succeeding lines up to but not including the end of
-        mail data indicator to be the message text.
-
-        -- RFC 2821, 4.1.1.4.
-
-        The receiver normally sends a 354 response to DATA, and then treats
-        the lines (strings ending in <CRLF> sequences, as described in
-        section 2.3.7) following the command as mail data from the sender.
-        This command causes the mail data to be appended to the mail data
-        buffer.  The mail data may contain any of the 128 ASCII character
-        codes, although experience has indicated that use of control
-        characters other than SP, HT, CR, and LF may cause problems and
-        SHOULD be avoided when possible.
-
-        -- RFC 2821, 4.3.2.
-
-        DATA
-            I: 354 -> data -> S: 250
-                                                E: 552, 554, 451, 452
-            E: 451, 554, 503
-        */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("DATA\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('354 Go ahead'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-        } catch (Exception $e) {
-            $this->fail('354 is the expected response to DATA');
-        }
-    }
-
-    public function testBadDataResponseCausesException()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("DATA\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('451 Bad'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-            $this->fail('354 is the expected response to DATA (not observed)');
-        } catch (Swift_TransportException $e) {
-        }
-    }
-
-    public function testMessageIsStreamedToBufferForData()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("DATA\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('354 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("\r\n.\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testBadResponseAfterDataTransmissionCausesException()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->once()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->once()
-                ->andReturn(['foo@bar' => null]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("DATA\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('354 OK'."\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("\r\n.\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn('554 Error'."\r\n");
-
-        $this->finishBuffer($buf);
-        try {
-            $smtp->start();
-            $smtp->send($message);
-            $this->fail('250 is the expected response after a DATA transmission (not observed)');
-        } catch (Swift_TransportException $e) {
-        }
-    }
-
-    public function testBccRecipientsAreRemovedFromHeaders()
-    {
-        /* -- RFC 2821, 7.2.
-
-     Addresses that do not appear in the message headers may appear in the
-     RCPT commands to an SMTP server for a number of reasons.  The two
-     most common involve the use of a mailing address as a "list exploder"
-     (a single address that resolves into multiple addresses) and the
-     appearance of "blind copies".  Especially when more than one RCPT
-     command is present, and in order to avoid defeating some of the
-     purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy
-     the full set of RCPT command arguments into the headers, either as
-     part of trace headers or as informational or private-extension
-     headers.  Since this rule is often violated in practice, and cannot
-     be enforced, sending SMTP systems that are aware of "bcc" use MAY
-     find it helpful to send each blind copy as a separate message
-     transaction containing only a single RCPT command.
-     */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['foo@bar' => null]);
-        $message->shouldReceive('getBcc')
-                ->zeroOrMoreTimes()
-                ->andReturn([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-        $message->shouldReceive('setBcc')
-                ->once()
-                ->with([]);
-        $message->shouldReceive('setBcc')
-                ->zeroOrMoreTimes();
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testMessageStateIsRestoredOnFailure()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['foo@bar' => null]);
-        $message->shouldReceive('getBcc')
-                ->zeroOrMoreTimes()
-                ->andReturn([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-        $message->shouldReceive('setBcc')
-                ->once()
-                ->with([]);
-        $message->shouldReceive('setBcc')
-                ->once()
-                ->with([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("MAIL FROM:<me@domain.com>\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("RCPT TO:<foo@bar>\r\n")
-            ->andReturn(2);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(2)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("DATA\r\n")
-            ->andReturn(3);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(3)
-            ->andReturn("451 No\r\n");
-
-        $this->finishBuffer($buf);
-
-        $smtp->start();
-        try {
-            $smtp->send($message);
-            $this->fail('A bad response was given so exception is expected');
-        } catch (Swift_TransportException $e) {
-        }
-    }
-
-    public function testStopSendsQuitCommand()
-    {
-        /* -- RFC 2821, 4.1.1.10.
-
-        This command specifies that the receiver MUST send an OK reply, and
-        then close the transmission channel.
-
-        The receiver MUST NOT intentionally close the transmission channel
-        until it receives and replies to a QUIT command (even if there was an
-        error).  The sender MUST NOT intentionally close the transmission
-        channel until it sends a QUIT command and SHOULD wait until it
-        receives the reply (even if there was an error response to a previous
-        command).  If the connection is closed prematurely due to violations
-        of the above or system or network failure, the server MUST cancel any
-        pending transaction, but not undo any previously completed
-        transaction, and generally MUST act as if the command or transaction
-        in progress had received a temporary error (i.e., a 4yz response).
-
-        The QUIT command may be issued at any time.
-
-        Syntax:
-            "QUIT" CRLF
-        */
-
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('write')
-            ->once()
-            ->with("QUIT\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn("221 Bye\r\n");
-        $buf->shouldReceive('terminate')
-            ->once();
-
-        $this->finishBuffer($buf);
-
-        $this->assertFalse($smtp->isStarted());
-        $smtp->start();
-        $this->assertTrue($smtp->isStarted());
-        $smtp->stop();
-        $this->assertFalse($smtp->isStarted());
-    }
-
-    public function testBufferCanBeFetched()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $ref = $smtp->getBuffer();
-        $this->assertEquals($buf, $ref);
-    }
-
-    public function testBufferCanBeWrittenToUsingExecuteCommand()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with("FOO\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with(1)
-            ->andReturn("250 OK\r\n");
-
-        $res = $smtp->executeCommand("FOO\r\n");
-        $this->assertEquals("250 OK\r\n", $res);
-    }
-
-    public function testResponseCodesAreValidated()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with("FOO\r\n")
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with(1)
-            ->andReturn("551 Not ok\r\n");
-
-        try {
-            $smtp->executeCommand("FOO\r\n", [250, 251]);
-            $this->fail('A 250 or 251 response was needed but 551 was returned.');
-        } catch (Swift_TransportException $e) {
-        }
-    }
-
-    public function testFailedRecipientsCanBeCollectedByReference()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['foo@bar' => null]);
-        $message->shouldReceive('getBcc')
-                ->zeroOrMoreTimes()
-                ->andReturn([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-        $message->shouldReceive('setBcc')
-                ->atLeast()->once()
-                ->with([]);
-        $message->shouldReceive('setBcc')
-                ->atLeast()->once()
-                ->with([
-                    'zip@button' => 'Zip Button',
-                    'test@domain' => 'Test user',
-                ]);
-
-        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(1);
-        $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')->once()->with("RCPT TO:<foo@bar>\r\n")->andReturn(2);
-        $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')->once()->with("RCPT TO:<zip@button>\r\n")->andReturn(3);
-        $buf->shouldReceive('readLine')->once()->with(3)->andReturn("500 Bad\r\n");
-        $buf->shouldReceive('write')->once()->with("RCPT TO:<test@domain>\r\n")->andReturn(4);
-        $buf->shouldReceive('readLine')->once()->with(4)->andReturn("500 Bad\r\n");
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertEquals(1, $smtp->send($message, $failures));
-    }
-
-    public function testSendingRegeneratesMessageId()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-        $message = $this->createMessage();
-        $message->shouldReceive('getFrom')
-                ->zeroOrMoreTimes()
-                ->andReturn(['me@domain.com' => 'Me']);
-        $message->shouldReceive('getTo')
-                ->zeroOrMoreTimes()
-                ->andReturn(['foo@bar' => null]);
-        $message->shouldReceive('generateId')
-                ->once();
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $smtp->send($message);
-    }
-
-    public function testPing()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("220 some.server.tld bleh\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with(Mockery::pattern('~^NOOP\r\n$~D'))
-            ->andReturn(1);
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(1)
-            ->andReturn('250 OK'."\r\n");
-
-        $this->finishBuffer($buf);
-        $this->assertTrue($smtp->ping());
-    }
-
-    public function testPingOnDeadConnection()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-
-        $buf->shouldReceive('initialize')
-            ->once();
-        $buf->shouldReceive('readLine')
-            ->once()
-            ->with(0)
-            ->andReturn("220 some.server.tld bleh\r\n");
-        $buf->shouldReceive('write')
-            ->once()
-            ->with(Mockery::pattern('~^NOOP\r\n$~D'))
-            ->andThrow('Swift_TransportException');
-
-        $this->finishBuffer($buf);
-        $smtp->start();
-        $this->assertTrue($smtp->isStarted());
-        $this->assertFalse($smtp->ping());
-        $this->assertFalse($smtp->isStarted());
-    }
-
-    public function testSetLocalDomain()
-    {
-        $buf = $this->getBuffer();
-        $smtp = $this->getTransport($buf);
-
-        $smtp->setLocalDomain('example.com');
-        $this->assertEquals('example.com', $smtp->getLocalDomain());
-
-        $smtp->setLocalDomain('192.168.0.1');
-        $this->assertEquals('[192.168.0.1]', $smtp->getLocalDomain());
-
-        $smtp->setLocalDomain('[192.168.0.1]');
-        $this->assertEquals('[192.168.0.1]', $smtp->getLocalDomain());
-
-        $smtp->setLocalDomain('fd00::');
-        $this->assertEquals('[IPv6:fd00::]', $smtp->getLocalDomain());
-
-        $smtp->setLocalDomain('[IPv6:fd00::]');
-        $this->assertEquals('[IPv6:fd00::]', $smtp->getLocalDomain());
-    }
-
-    protected function getBuffer()
-    {
-        return $this->getMockery('Swift_Transport_IoBuffer')->shouldIgnoreMissing();
-    }
-
-    protected function createMessage()
-    {
-        return $this->getMockery('Swift_Mime_SimpleMessage')->shouldIgnoreMissing();
-    }
-
-    protected function finishBuffer($buf)
-    {
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with(0)
-            ->andReturn('220 server.com foo'."\r\n");
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with(Mockery::pattern('~^(EH|HE)LO .*?\r\n$~D'))
-            ->andReturn($x = uniqid('', true));
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with($x)
-            ->andReturn('250 ServerName'."\r\n");
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with(Mockery::pattern('~^MAIL FROM:<.*?>\r\n$~D'))
-            ->andReturn($x = uniqid('', true));
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with($x)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with(Mockery::pattern('~^RCPT TO:<.*?>\r\n$~D'))
-            ->andReturn($x = uniqid('', true));
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with($x)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with("DATA\r\n")
-            ->andReturn($x = uniqid('', true));
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with($x)
-            ->andReturn("354 OK\r\n");
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with("\r\n.\r\n")
-            ->andReturn($x = uniqid('', true));
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with($x)
-            ->andReturn("250 OK\r\n");
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->with("RSET\r\n")
-            ->andReturn($x = uniqid('', true));
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->with($x)
-            ->andReturn("250 OK\r\n");
-
-        $buf->shouldReceive('write')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-        $buf->shouldReceive('readLine')
-            ->zeroOrMoreTimes()
-            ->andReturn(false);
-    }
-}
diff --git a/tests/unit/Swift/Transport/AbstractSmtpTestCase.php b/tests/unit/Swift/Transport/AbstractSmtpTestCase.php
new file mode 100644
index 0000000..c8429ef
--- /dev/null
+++ b/tests/unit/Swift/Transport/AbstractSmtpTestCase.php
@@ -0,0 +1,1311 @@
+<?php
+
+abstract class Swift_Transport_AbstractSmtpTestCase extends \SwiftMailerTestCase
+{
+    abstract protected function getTransport($buf);
+
+    public function testStartAccepts220ServiceGreeting()
+    {
+        /* -- RFC 2821, 4.2.
+
+     Greeting = "220 " Domain [ SP text ] CRLF
+
+     -- RFC 2822, 4.3.2.
+
+     CONNECTION ESTABLISHMENT
+         S: 220
+         E: 554
+        */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("220 some.server.tld bleh\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+            $smtp->start();
+            $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection');
+        } catch (Exception $e) {
+            $this->fail('220 is a valid SMTP greeting and should be accepted');
+        }
+    }
+
+    public function testBadGreetingCausesException()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("554 I'm busy\r\n");
+        $this->finishBuffer($buf);
+        try {
+            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+            $smtp->start();
+            $this->fail('554 greeting indicates an error and should cause an exception');
+        } catch (Swift_TransportException $e) {
+            $this->assertFalse($smtp->isStarted(), '%s: start() should have failed');
+        }
+    }
+
+    public function testStartSendsHeloToInitiate()
+    {
+        /* -- RFC 2821, 3.2.
+
+            3.2 Client Initiation
+
+         Once the server has sent the welcoming message and the client has
+         received it, the client normally sends the EHLO command to the
+         server, indicating the client's identity.  In addition to opening the
+         session, use of EHLO indicates that the client is able to process
+         service extensions and requests that the server provide a list of the
+         extensions it supports.  Older SMTP systems which are unable to
+         support service extensions and contemporary clients which do not
+         require service extensions in the mail session being initiated, MAY
+         use HELO instead of EHLO.  Servers MUST NOT return the extended
+         EHLO-style response to a HELO command.  For a particular connection
+         attempt, if the server returns a "command not recognized" response to
+         EHLO, the client SHOULD be able to fall back and send HELO.
+
+         In the EHLO command the host sending the command identifies itself;
+         the command may be interpreted as saying "Hello, I am <domain>" (and,
+         in the case of EHLO, "and I support service extension requests").
+
+       -- RFC 2281, 4.1.1.1.
+
+       ehlo            = "EHLO" SP Domain CRLF
+       helo            = "HELO" SP Domain CRLF
+
+       -- RFC 2821, 4.3.2.
+
+       EHLO or HELO
+           S: 250
+           E: 504, 550
+
+     */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("220 some.server.tld bleh\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with(Mockery::pattern('~^HELO example.org\r\n$~D'))
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 ServerName'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+        } catch (Exception $e) {
+            $this->fail('Starting SMTP should send HELO and accept 250 response');
+        }
+    }
+
+    public function testInvalidHeloResponseCausesException()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("220 some.server.tld bleh\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with(Mockery::pattern('~^HELO example.org\r\n$~D'))
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('504 WTF'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
+            $smtp->start();
+            $this->fail('Non 250 HELO response should raise Exception');
+        } catch (Swift_TransportException $e) {
+            $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed');
+        }
+    }
+
+    public function testDomainNameIsPlacedInHelo()
+    {
+        /* -- RFC 2821, 4.1.4.
+
+       The SMTP client MUST, if possible, ensure that the domain parameter
+       to the EHLO command is a valid principal host name (not a CNAME or MX
+       name) for its host.  If this is not possible (e.g., when the client's
+       address is dynamically assigned and the client does not have an
+       obvious name), an address literal SHOULD be substituted for the
+       domain name and supplemental information provided that will assist in
+       identifying the client.
+        */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("220 some.server.tld bleh\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("HELO mydomain.com\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 ServerName'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->setLocalDomain('mydomain.com');
+        $smtp->start();
+    }
+
+    public function testSuccessfulMailCommand()
+    {
+        /* -- RFC 2821, 3.3.
+
+        There are three steps to SMTP mail transactions.  The transaction
+        starts with a MAIL command which gives the sender identification.
+
+        .....
+
+        The first step in the procedure is the MAIL command.
+
+            MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
+
+        -- RFC 2821, 4.1.1.2.
+
+        Syntax:
+
+            "MAIL FROM:" ("<>" / Reverse-Path)
+                       [SP Mail-parameters] CRLF
+        -- RFC 2821, 4.1.2.
+
+        Reverse-path = Path
+            Forward-path = Path
+            Path = "<" [ A-d-l ":" ] Mailbox ">"
+            A-d-l = At-domain *( "," A-d-l )
+                        ; Note that this form, the so-called "source route",
+                        ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be
+                        ; ignored.
+            At-domain = "@" domain
+
+        -- RFC 2821, 4.3.2.
+
+        MAIL
+            S: 250
+            E: 552, 451, 452, 550, 553, 503
+        */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<me@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn("250 OK\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+        } catch (Exception $e) {
+            $this->fail('MAIL FROM should accept a 250 response');
+        }
+    }
+
+    public function testInvalidResponseCodeFromMailCausesException()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<me@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('553 Bad'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+            $this->fail('MAIL FROM should accept a 250 response');
+        } catch (Swift_TransportException $e) {
+        }
+    }
+
+    public function testSenderIsPreferredOverFrom()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getSender')
+                ->once()
+                ->andReturn(['another@domain.com' => 'Someone']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<another@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testReturnPathIsPreferredOverSender()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getSender')
+                ->once()
+                ->andReturn(['another@domain.com' => 'Someone']);
+        $message->shouldReceive('getReturnPath')
+                ->once()
+                ->andReturn('more@domain.com');
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<more@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testSuccessfulRcptCommandWith250Response()
+    {
+        /* -- RFC 2821, 3.3.
+
+     The second step in the procedure is the RCPT command.
+
+            RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
+
+     The first or only argument to this command includes a forward-path
+     (normally a mailbox and domain, always surrounded by "<" and ">"
+     brackets) identifying one recipient.  If accepted, the SMTP server
+     returns a 250 OK reply and stores the forward-path.  If the recipient
+     is known not to be a deliverable address, the SMTP server returns a
+     550 reply, typically with a string such as "no such user - " and the
+     mailbox name (other circumstances and reply codes are possible).
+     This step of the procedure can be repeated any number of times.
+
+        -- RFC 2821, 4.1.1.3.
+
+        This command is used to identify an individual recipient of the mail
+        data; multiple recipients are specified by multiple use of this
+        command.  The argument field contains a forward-path and may contain
+        optional parameters.
+
+        The forward-path normally consists of the required destination
+        mailbox.  Sending systems SHOULD not generate the optional list of
+        hosts known as a source route.
+
+        .......
+
+        "RCPT TO:" ("<Postmaster@" domain ">" / "<Postmaster>" / Forward-Path)
+                                        [SP Rcpt-parameters] CRLF
+
+        -- RFC 2821, 4.2.2.
+
+            250 Requested mail action okay, completed
+            251 User not local; will forward to <forward-path>
+         (See section 3.4)
+            252 Cannot VRFY user, but will accept message and attempt
+                    delivery
+
+        -- RFC 2821, 4.3.2.
+
+        RCPT
+            S: 250, 251 (but see section 3.4 for discussion of 251 and 551)
+            E: 550, 551, 552, 553, 450, 451, 452, 503, 550
+        */
+
+        //We'll treat 252 as accepted since it isn't really a failure
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<me@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+        } catch (Exception $e) {
+            $this->fail('RCPT TO should accept a 250 response');
+        }
+    }
+
+    public function testUtf8AddressWithIdnEncoder()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@dömain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bär' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<me@xn--dmain-jua.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@xn--br-via>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testUtf8AddressWithUtf8Encoder()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf, null, new Swift_AddressEncoder_Utf8AddressEncoder());
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['më@dömain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['föö@bär' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<më@dömain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<föö@bär>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testNonEncodableSenderCausesException()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['më@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+            $this->fail('më@domain.com cannot be encoded (not observed)');
+        } catch (Swift_AddressEncoderException $e) {
+            $this->assertEquals('më@domain.com', $e->getAddress());
+        }
+    }
+
+    public function testMailFromCommandIsOnlySentOncePerMessage()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<me@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->never()
+            ->with("MAIL FROM:<me@domain.com>\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testMultipleRecipientsSendsMultipleRcpt()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn([
+                    'foo@bar' => null,
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                    'tëst@domain' => 'Test user',
+                ]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<zip@button>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<test@domain>\r\n")
+            ->andReturn(3);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(3)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testCcRecipientsSendsMultipleRcpt()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $message->shouldReceive('getCc')
+                ->once()
+                ->andReturn([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<zip@button>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<test@domain>\r\n")
+            ->andReturn(3);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(3)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testSendReturnsNumberOfSuccessfulRecipients()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $message->shouldReceive('getCc')
+                ->once()
+                ->andReturn([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<zip@button>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('501 Nobody here'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<test@domain>\r\n")
+            ->andReturn(3);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(3)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(2, $smtp->send($message),
+            '%s: 1 of 3 recipients failed so 2 should be returned'
+            );
+    }
+
+    public function testRsetIsSentIfNoSuccessfulRecipients()
+    {
+        /* --RFC 2821, 4.1.1.5.
+
+        This command specifies that the current mail transaction will be
+        aborted.  Any stored sender, recipients, and mail data MUST be
+        discarded, and all buffers and state tables cleared.  The receiver
+        MUST send a "250 OK" reply to a RSET command with no arguments.  A
+        reset command may be issued by the client at any time.
+
+        -- RFC 2821, 4.3.2.
+
+        RSET
+            S: 250
+        */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('503 Bad'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RSET\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(0, $smtp->send($message),
+            '%s: 1 of 1 recipients failed so 0 should be returned'
+            );
+    }
+
+    public function testSuccessfulDataCommand()
+    {
+        /* -- RFC 2821, 3.3.
+
+        The third step in the procedure is the DATA command (or some
+        alternative specified in a service extension).
+
+                    DATA <CRLF>
+
+        If accepted, the SMTP server returns a 354 Intermediate reply and
+        considers all succeeding lines up to but not including the end of
+        mail data indicator to be the message text.
+
+        -- RFC 2821, 4.1.1.4.
+
+        The receiver normally sends a 354 response to DATA, and then treats
+        the lines (strings ending in <CRLF> sequences, as described in
+        section 2.3.7) following the command as mail data from the sender.
+        This command causes the mail data to be appended to the mail data
+        buffer.  The mail data may contain any of the 128 ASCII character
+        codes, although experience has indicated that use of control
+        characters other than SP, HT, CR, and LF may cause problems and
+        SHOULD be avoided when possible.
+
+        -- RFC 2821, 4.3.2.
+
+        DATA
+            I: 354 -> data -> S: 250
+                                                E: 552, 554, 451, 452
+            E: 451, 554, 503
+        */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("DATA\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('354 Go ahead'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+        } catch (Exception $e) {
+            $this->fail('354 is the expected response to DATA');
+        }
+    }
+
+    public function testBadDataResponseCausesException()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("DATA\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('451 Bad'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+            $this->fail('354 is the expected response to DATA (not observed)');
+        } catch (Swift_TransportException $e) {
+        }
+    }
+
+    public function testMessageIsStreamedToBufferForData()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("DATA\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('354 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("\r\n.\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testBadResponseAfterDataTransmissionCausesException()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->once()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->once()
+                ->andReturn(['foo@bar' => null]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("DATA\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('354 OK'."\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("\r\n.\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn('554 Error'."\r\n");
+
+        $this->finishBuffer($buf);
+        try {
+            $smtp->start();
+            $smtp->send($message);
+            $this->fail('250 is the expected response after a DATA transmission (not observed)');
+        } catch (Swift_TransportException $e) {
+        }
+    }
+
+    public function testBccRecipientsAreRemovedFromHeaders()
+    {
+        /* -- RFC 2821, 7.2.
+
+     Addresses that do not appear in the message headers may appear in the
+     RCPT commands to an SMTP server for a number of reasons.  The two
+     most common involve the use of a mailing address as a "list exploder"
+     (a single address that resolves into multiple addresses) and the
+     appearance of "blind copies".  Especially when more than one RCPT
+     command is present, and in order to avoid defeating some of the
+     purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy
+     the full set of RCPT command arguments into the headers, either as
+     part of trace headers or as informational or private-extension
+     headers.  Since this rule is often violated in practice, and cannot
+     be enforced, sending SMTP systems that are aware of "bcc" use MAY
+     find it helpful to send each blind copy as a separate message
+     transaction containing only a single RCPT command.
+     */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['foo@bar' => null]);
+        $message->shouldReceive('getBcc')
+                ->zeroOrMoreTimes()
+                ->andReturn([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+        $message->shouldReceive('setBcc')
+                ->once()
+                ->with([]);
+        $message->shouldReceive('setBcc')
+                ->zeroOrMoreTimes();
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testMessageStateIsRestoredOnFailure()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['foo@bar' => null]);
+        $message->shouldReceive('getBcc')
+                ->zeroOrMoreTimes()
+                ->andReturn([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+        $message->shouldReceive('setBcc')
+                ->once()
+                ->with([]);
+        $message->shouldReceive('setBcc')
+                ->once()
+                ->with([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("MAIL FROM:<me@domain.com>\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("RCPT TO:<foo@bar>\r\n")
+            ->andReturn(2);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(2)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("DATA\r\n")
+            ->andReturn(3);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(3)
+            ->andReturn("451 No\r\n");
+
+        $this->finishBuffer($buf);
+
+        $smtp->start();
+        try {
+            $smtp->send($message);
+            $this->fail('A bad response was given so exception is expected');
+        } catch (Swift_TransportException $e) {
+        }
+    }
+
+    public function testStopSendsQuitCommand()
+    {
+        /* -- RFC 2821, 4.1.1.10.
+
+        This command specifies that the receiver MUST send an OK reply, and
+        then close the transmission channel.
+
+        The receiver MUST NOT intentionally close the transmission channel
+        until it receives and replies to a QUIT command (even if there was an
+        error).  The sender MUST NOT intentionally close the transmission
+        channel until it sends a QUIT command and SHOULD wait until it
+        receives the reply (even if there was an error response to a previous
+        command).  If the connection is closed prematurely due to violations
+        of the above or system or network failure, the server MUST cancel any
+        pending transaction, but not undo any previously completed
+        transaction, and generally MUST act as if the command or transaction
+        in progress had received a temporary error (i.e., a 4yz response).
+
+        The QUIT command may be issued at any time.
+
+        Syntax:
+            "QUIT" CRLF
+        */
+
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('write')
+            ->once()
+            ->with("QUIT\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn("221 Bye\r\n");
+        $buf->shouldReceive('terminate')
+            ->once();
+
+        $this->finishBuffer($buf);
+
+        $this->assertFalse($smtp->isStarted());
+        $smtp->start();
+        $this->assertTrue($smtp->isStarted());
+        $smtp->stop();
+        $this->assertFalse($smtp->isStarted());
+    }
+
+    public function testBufferCanBeFetched()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $ref = $smtp->getBuffer();
+        $this->assertEquals($buf, $ref);
+    }
+
+    public function testBufferCanBeWrittenToUsingExecuteCommand()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with("FOO\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with(1)
+            ->andReturn("250 OK\r\n");
+
+        $res = $smtp->executeCommand("FOO\r\n");
+        $this->assertEquals("250 OK\r\n", $res);
+    }
+
+    public function testResponseCodesAreValidated()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with("FOO\r\n")
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with(1)
+            ->andReturn("551 Not ok\r\n");
+
+        try {
+            $smtp->executeCommand("FOO\r\n", [250, 251]);
+            $this->fail('A 250 or 251 response was needed but 551 was returned.');
+        } catch (Swift_TransportException $e) {
+        }
+    }
+
+    public function testFailedRecipientsCanBeCollectedByReference()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['foo@bar' => null]);
+        $message->shouldReceive('getBcc')
+                ->zeroOrMoreTimes()
+                ->andReturn([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+        $message->shouldReceive('setBcc')
+                ->atLeast()->once()
+                ->with([]);
+        $message->shouldReceive('setBcc')
+                ->atLeast()->once()
+                ->with([
+                    'zip@button' => 'Zip Button',
+                    'test@domain' => 'Test user',
+                ]);
+
+        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(1);
+        $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')->once()->with("RCPT TO:<foo@bar>\r\n")->andReturn(2);
+        $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')->once()->with("RCPT TO:<zip@button>\r\n")->andReturn(3);
+        $buf->shouldReceive('readLine')->once()->with(3)->andReturn("500 Bad\r\n");
+        $buf->shouldReceive('write')->once()->with("RCPT TO:<test@domain>\r\n")->andReturn(4);
+        $buf->shouldReceive('readLine')->once()->with(4)->andReturn("500 Bad\r\n");
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertEquals(1, $smtp->send($message, $failures));
+    }
+
+    public function testSendingRegeneratesMessageId()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+        $message = $this->createMessage();
+        $message->shouldReceive('getFrom')
+                ->zeroOrMoreTimes()
+                ->andReturn(['me@domain.com' => 'Me']);
+        $message->shouldReceive('getTo')
+                ->zeroOrMoreTimes()
+                ->andReturn(['foo@bar' => null]);
+        $message->shouldReceive('generateId')
+                ->once();
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $smtp->send($message);
+    }
+
+    public function testPing()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("220 some.server.tld bleh\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with(Mockery::pattern('~^NOOP\r\n$~D'))
+            ->andReturn(1);
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(1)
+            ->andReturn('250 OK'."\r\n");
+
+        $this->finishBuffer($buf);
+        $this->assertTrue($smtp->ping());
+    }
+
+    public function testPingOnDeadConnection()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+
+        $buf->shouldReceive('initialize')
+            ->once();
+        $buf->shouldReceive('readLine')
+            ->once()
+            ->with(0)
+            ->andReturn("220 some.server.tld bleh\r\n");
+        $buf->shouldReceive('write')
+            ->once()
+            ->with(Mockery::pattern('~^NOOP\r\n$~D'))
+            ->andThrow('Swift_TransportException');
+
+        $this->finishBuffer($buf);
+        $smtp->start();
+        $this->assertTrue($smtp->isStarted());
+        $this->assertFalse($smtp->ping());
+        $this->assertFalse($smtp->isStarted());
+    }
+
+    public function testSetLocalDomain()
+    {
+        $buf = $this->getBuffer();
+        $smtp = $this->getTransport($buf);
+
+        $smtp->setLocalDomain('example.com');
+        $this->assertEquals('example.com', $smtp->getLocalDomain());
+
+        $smtp->setLocalDomain('192.168.0.1');
+        $this->assertEquals('[192.168.0.1]', $smtp->getLocalDomain());
+
+        $smtp->setLocalDomain('[192.168.0.1]');
+        $this->assertEquals('[192.168.0.1]', $smtp->getLocalDomain());
+
+        $smtp->setLocalDomain('fd00::');
+        $this->assertEquals('[IPv6:fd00::]', $smtp->getLocalDomain());
+
+        $smtp->setLocalDomain('[IPv6:fd00::]');
+        $this->assertEquals('[IPv6:fd00::]', $smtp->getLocalDomain());
+    }
+
+    protected function getBuffer()
+    {
+        return $this->getMockery('Swift_Transport_IoBuffer')->shouldIgnoreMissing();
+    }
+
+    protected function createMessage()
+    {
+        return $this->getMockery('Swift_Mime_SimpleMessage')->shouldIgnoreMissing();
+    }
+
+    protected function finishBuffer($buf)
+    {
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with(0)
+            ->andReturn('220 server.com foo'."\r\n");
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with(Mockery::pattern('~^(EH|HE)LO .*?\r\n$~D'))
+            ->andReturn($x = uniqid('', true));
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with($x)
+            ->andReturn('250 ServerName'."\r\n");
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with(Mockery::pattern('~^MAIL FROM:<.*?>\r\n$~D'))
+            ->andReturn($x = uniqid('', true));
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with($x)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with(Mockery::pattern('~^RCPT TO:<.*?>\r\n$~D'))
+            ->andReturn($x = uniqid('', true));
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with($x)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with("DATA\r\n")
+            ->andReturn($x = uniqid('', true));
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with($x)
+            ->andReturn("354 OK\r\n");
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with("\r\n.\r\n")
+            ->andReturn($x = uniqid('', true));
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with($x)
+            ->andReturn("250 OK\r\n");
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->with("RSET\r\n")
+            ->andReturn($x = uniqid('', true));
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->with($x)
+            ->andReturn("250 OK\r\n");
+
+        $buf->shouldReceive('write')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+        $buf->shouldReceive('readLine')
+            ->zeroOrMoreTimes()
+            ->andReturn(false);
+    }
+}
