diff --git a/.travis.yml b/.travis.yml
index 486abd679..79016839d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
-language: php
 sudo: false
 
+language: php
+
 matrix:
   include:
     - php: 5.3
@@ -41,6 +42,24 @@ matrix:
 cache:
   directories:
     - $HOME/.composer/cache/files
+    - stripe-mock
+
+env:
+  global:
+    - STRIPE_MOCK_VERSION=0.5.0
+
+before_install:
+  # Unpack and start stripe-mock so that the test suite can talk to it
+  - |
+    if [ ! -d "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}" ]; then
+      mkdir -p stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/
+      curl -L "https://github.com/stripe/stripe-mock/releases/download/v${STRIPE_MOCK_VERSION}/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" -o "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz"
+      tar -zxf "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" -C "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/"
+    fi
+  - |
+    stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/stripe-mock > /dev/null &
+    STRIPE_MOCK_PID=$!
 
 script: ./build.php ${AUTOLOAD}
+
 after_script: ./vendor/bin/coveralls -v
diff --git a/tests/AccountTest.php b/tests/AccountTest.php
deleted file mode 100644
index b313e53d4..000000000
--- a/tests/AccountTest.php
+++ /dev/null
@@ -1,470 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class AccountTest extends TestCase
-{
-    private function managedAccountResponse($id)
-    {
-        return array(
-            'id' => $id,
-            'currencies_supported' => array('usd', 'aed', 'afn', '...'),
-            'object' => 'account',
-            'business_name' => 'Stripe.com',
-            'bank_accounts' => array(
-                'object' => 'list',
-                'total_count' => 0,
-                'has_more' => false,
-                'url' => '/v1/accounts/' . $id . '/bank_accounts',
-                'data' => array()
-            ),
-            'verification' => array(
-                'fields_needed' => array(
-                    'product_description',
-                    'business_url',
-                    'support_phone',
-                    'bank_account',
-                    'tos_acceptance.ip',
-                    'tos_acceptance.date'
-                ),
-                'due_by' => null,
-                'contacted' => false
-            ),
-            'tos_acceptance' => array(
-                'ip' => null,
-                'date' => null,
-                'user_agent' => null
-            ),
-            'legal_entity' => array(
-                'type' => null,
-                'business_name' => null,
-                'address' => array(
-                    'line1' => null,
-                    'line2' => null,
-                    'city' => null,
-                    'state' => null,
-                    'postal_code' => null,
-                    'country' => 'US'
-                ),
-                'first_name' => null,
-                'last_name' => null,
-                'additional_owners' => null,
-                'verification' => array(
-                    'status' => 'unverified',
-                    'document' => null,
-                    'details' => null
-                )
-            )
-        );
-    }
-
-    private function deletedAccountResponse($id)
-    {
-        return array(
-            'id' => $id,
-            'deleted' => true
-        );
-    }
-
-    public function testBasicRetrieve()
-    {
-        $this->mockRequest('GET', '/v1/account', array(), $this->managedAccountResponse('acct_ABC'));
-        $account = Account::retrieve();
-        $this->assertSame($account->id, 'acct_ABC');
-    }
-
-    public function testIDRetrieve()
-    {
-        $this->mockRequest('GET', '/v1/accounts/acct_DEF', array(), $this->managedAccountResponse('acct_DEF'));
-        $account = Account::retrieve('acct_DEF');
-        $this->assertSame($account->id, 'acct_DEF');
-    }
-
-    public function testCreate()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts',
-            array('managed' => 'true'),
-            $this->managedAccountResponse('acct_ABC')
-        );
-        $account = Account::create(array(
-            'managed' => true
-        ));
-        $this->assertSame($account->id, 'acct_ABC');
-    }
-
-    public function testDelete()
-    {
-        $account = self::createTestAccount();
-
-        $this->mockRequest(
-            'DELETE',
-            '/v1/accounts/' . $account->id,
-            array(),
-            $this->deletedAccountResponse('acct_ABC')
-        );
-        $deleted = $account->delete();
-        $this->assertSame($deleted->id, $account->id);
-        $this->assertTrue($deleted->deleted);
-    }
-
-    public function testReject()
-    {
-        $account = self::createTestAccount();
-
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/' . $account->id . '/reject',
-            array('reason' => 'fraud'),
-            $this->deletedAccountResponse('acct_ABC')
-        );
-        $rejected = $account->reject(array('reason' => 'fraud'));
-        $this->assertSame($rejected->id, $account->id);
-    }
-
-    public function testSaveLegalEntity()
-    {
-        $response = $this->managedAccountResponse('acct_ABC');
-        $this->mockRequest('POST', '/v1/accounts', array('managed' => 'true'), $response);
-
-        $response['legal_entity']['first_name'] = 'Bob';
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('first_name' => 'Bob')),
-            $response
-        );
-
-        $account = Account::create(array('managed' => true));
-        $account->legal_entity->first_name = 'Bob';
-        $account->save();
-
-        $this->assertSame('Bob', $account->legal_entity->first_name);
-    }
-
-    public function testUpdateLegalEntity()
-    {
-        $response = $this->managedAccountResponse('acct_ABC');
-        $this->mockRequest('POST', '/v1/accounts', array('managed' => 'true'), $response);
-
-        $response['legal_entity']['first_name'] = 'Bob';
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('first_name' => 'Bob')),
-            $response
-        );
-
-        $account = Account::create(array('managed' => true));
-        $account = Account::update($account['id'], array(
-          'legal_entity' => array(
-            'first_name' => 'Bob'
-          )
-        ));
-
-        $this->assertSame('Bob', $account->legal_entity->first_name);
-    }
-
-    public function testCreateAdditionalOwners()
-    {
-        $request = array(
-            'managed' => true,
-            'country' => 'GB',
-            'legal_entity' => array(
-                'additional_owners' => array(
-                    0 => array(
-                        'dob' => array(
-                            'day' => 12,
-                            'month' => 5,
-                            'year' => 1970,
-                        ),
-                        'first_name' => 'xgvukvfrde',
-                        'last_name' => 'rtcyvubhy',
-                    ),
-                    1 => array(
-                        'dob' => array(
-                            'day' => 8,
-                            'month' => 4,
-                            'year' => 1979,
-                        ),
-                        'first_name' => 'yutreuk',
-                        'last_name' => 'dfcgvhbjihmv',
-                    ),
-                ),
-            ),
-        );
-
-        $acct = Account::create($request);
-        $response = $acct->__toArray(true);
-
-        $req_ao = $request['legal_entity']['additional_owners'];
-        $resp_ao = $response['legal_entity']['additional_owners'];
-
-        $this->assertSame($req_ao[0]['dob'], $resp_ao[0]['dob']);
-        $this->assertSame($req_ao[1]['dob'], $resp_ao[1]['dob']);
-
-        $this->assertSame($req_ao[0]['first_name'], $resp_ao[0]['first_name']);
-        $this->assertSame($req_ao[1]['first_name'], $resp_ao[1]['first_name']);
-    }
-
-    public function testUpdateAdditionalOwners()
-    {
-        $response = $this->managedAccountResponse('acct_ABC');
-        $this->mockRequest('POST', '/v1/accounts', array('managed' => 'true'), $response);
-
-        $response['legal_entity']['additional_owners'] = array(array(
-            'first_name' => 'Bob',
-            'last_name' => null,
-            'address' => array(
-                'line1' => null,
-                'line2' => null,
-                'city' => null,
-                'state' => null,
-                'postal_code' => null,
-                'country' => null
-            ),
-            'verification' => array(
-                'status' => 'unverified',
-                'document' => null,
-                'details' => null
-            )
-        ));
-
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('additional_owners' => array(array('first_name' => 'Bob')))),
-            $response
-        );
-
-        $response['legal_entity']['additional_owners'][0]['last_name'] = 'Smith';
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('additional_owners' => array(array('last_name' => 'Smith')))),
-            $response
-        );
-
-        $response['legal_entity']['additional_owners'][0]['last_name'] = 'Johnson';
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('additional_owners' => array(array('last_name' => 'Johnson')))),
-            $response
-        );
-
-        $response['legal_entity']['additional_owners'][0]['verification']['document'] = 'file_123';
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('additional_owners' => array(array('verification' => array('document' => 'file_123'))))),
-            $response
-        );
-
-        $response['legal_entity']['additional_owners'][1] = array(
-            'first_name' => 'Jane',
-            'last_name' => 'Doe'
-        );
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_ABC',
-            array('legal_entity' => array('additional_owners' => array(1 => array('first_name' => 'Jane')))),
-            $response
-        );
-
-        $account = Account::create(array('managed' => true));
-        $account->legal_entity->additional_owners = array(array('first_name' => 'Bob'));
-        $account->save();
-        $this->assertSame(1, count($account->legal_entity->additional_owners));
-        $this->assertSame('Bob', $account->legal_entity->additional_owners[0]->first_name);
-
-        $account->legal_entity->additional_owners[0]->last_name = 'Smith';
-        $account->save();
-        $this->assertSame(1, count($account->legal_entity->additional_owners));
-        $this->assertSame('Smith', $account->legal_entity->additional_owners[0]->last_name);
-
-        $account['legal_entity']['additional_owners'][0]['last_name'] = 'Johnson';
-        $account->save();
-        $this->assertSame(1, count($account->legal_entity->additional_owners));
-        $this->assertSame('Johnson', $account->legal_entity->additional_owners[0]->last_name);
-
-        $account->legal_entity->additional_owners[0]->verification->document = 'file_123';
-        $account->save();
-        $this->assertSame('file_123', $account->legal_entity->additional_owners[0]->verification->document);
-
-        $account->legal_entity->additional_owners[1] = array('first_name' => 'Jane');
-        $account->save();
-        $this->assertSame(2, count($account->legal_entity->additional_owners));
-        $this->assertSame('Jane', $account->legal_entity->additional_owners[1]->first_name);
-    }
-
-    public function testLoginLinkCreation()
-    {
-        $accountId = 'acct_EXPRESS';
-        $mockExpress = array(
-            'id' => $accountId,
-            'object' => 'account',
-            'login_links' => array(
-                'object' => 'list',
-                'data' => array(),
-                'has_more' => false,
-                'url' =>  "/v1/accounts/$accountId/login_links"
-            )
-        );
-
-        $this->mockRequest('GET', "/v1/accounts/$accountId", array(), $mockExpress);
-
-        $mockLoginLink = array(
-            'object' => 'login_link',
-            'created' => 1493820886,
-            'url' => "https://connect.stripe.com/$accountId/AAAAAAAA"
-        );
-
-        $this->mockRequest('POST', "/v1/accounts/$accountId/login_links", array(), $mockLoginLink);
-
-        $account = Account::retrieve($accountId);
-        $loginLink = $account->login_links->create();
-        $this->assertSame('login_link', $loginLink->object);
-        $this->assertSame('Stripe\LoginLink', get_class($loginLink));
-    }
-
-    public function testDeauthorize()
-    {
-        Stripe::setClientId('ca_test');
-
-        $accountId = 'acct_test_deauth';
-        $mockAccount = array(
-            'id' => $accountId,
-            'object' => 'account',
-        );
-
-        $this->mockRequest('GET', "/v1/accounts/$accountId", array(), $mockAccount);
-
-        $this->mockRequest(
-            'POST',
-            '/oauth/deauthorize',
-            array(
-                'client_id' => 'ca_test',
-                'stripe_user_id' => $accountId,
-            ),
-            array(
-                'stripe_user_id' => $accountId,
-            ),
-            200,
-            Stripe::$connectBase
-        );
-
-        $account = Account::retrieve($accountId);
-        $account->deauthorize();
-
-        Stripe::setClientId(null);
-    }
-
-    public function testStaticCreateExternalAccount()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_123/external_accounts',
-            array('source' => 'btok_123'),
-            array('id' => 'ba_123', 'object' => 'bank_account')
-        );
-
-        $externalAccount = Account::createExternalAccount(
-            'acct_123',
-            array('source' => 'btok_123')
-        );
-
-        $this->assertSame('ba_123', $externalAccount->id);
-        $this->assertSame('bank_account', $externalAccount->object);
-    }
-
-    public function testStaticRetrieveExternalAccount()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/accounts/acct_123/external_accounts/ba_123',
-            array(),
-            array('id' => 'ba_123', 'object' => 'bank_account')
-        );
-
-        $externalAccount = Account::retrieveExternalAccount(
-            'acct_123',
-            'ba_123'
-        );
-
-        $this->assertSame('ba_123', $externalAccount->id);
-        $this->assertSame('bank_account', $externalAccount->object);
-    }
-
-    public function testStaticUpdateExternalAccount()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_123/external_accounts/ba_123',
-            array('metadata' => array('foo' => 'bar')),
-            array('id' => 'ba_123', 'object' => 'bank_account')
-        );
-
-        $externalAccount = Account::updateExternalAccount(
-            'acct_123',
-            'ba_123',
-            array('metadata' => array('foo' => 'bar'))
-        );
-
-        $this->assertSame('ba_123', $externalAccount->id);
-        $this->assertSame('bank_account', $externalAccount->object);
-    }
-
-    public function testStaticDeleteExternalAccount()
-    {
-        $this->mockRequest(
-            'DELETE',
-            '/v1/accounts/acct_123/external_accounts/ba_123',
-            array(),
-            array('id' => 'ba_123', 'deleted' => true)
-        );
-
-        $externalAccount = Account::deleteExternalAccount(
-            'acct_123',
-            'ba_123'
-        );
-
-        $this->assertSame('ba_123', $externalAccount->id);
-        $this->assertSame(true, $externalAccount->deleted);
-    }
-
-    public function testStaticAllExternalAccounts()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/accounts/acct_123/external_accounts',
-            array(),
-            array('object' => 'list', 'data' => array())
-        );
-
-        $externalAccounts = Account::allExternalAccounts(
-            'acct_123'
-        );
-
-        $this->assertSame('list', $externalAccounts->object);
-        $this->assertEmpty($externalAccounts->data);
-    }
-
-    public function testStaticCreateLoginLink()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/accounts/acct_123/login_links',
-            array(),
-            array('object' => 'login_link', 'url' => 'https://example.com')
-        );
-
-        $loginLink = Account::createLoginLink(
-            'acct_123'
-        );
-
-        $this->assertSame('login_link', $loginLink->object);
-        $this->assertSame('https://example.com', $loginLink->url);
-    }
-}
diff --git a/tests/ApplePayDomainTest.php b/tests/ApplePayDomainTest.php
deleted file mode 100644
index ebb3e7cfa..000000000
--- a/tests/ApplePayDomainTest.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ApplePayDomainTest extends TestCase
-{
-    public function testCreation()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/apple_pay/domains',
-            array('domain_name' => 'test.com'),
-            array(
-                'id' => 'apwc_create',
-                'object' => 'apple_pay_domain'
-            )
-        );
-        $d = ApplePayDomain::create(array(
-            'domain_name' => 'test.com'
-        ));
-        $this->assertSame('apwc_create', $d->id);
-        $this->assertInstanceOf('Stripe\\ApplePayDomain', $d);
-    }
-
-    public function testRetrieve()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/apple_pay/domains/apwc_retrieve',
-            array(),
-            array(
-                'id' => 'apwc_retrieve',
-                'object' => 'apple_pay_domain'
-            )
-        );
-        $d = ApplePayDomain::retrieve('apwc_retrieve');
-        $this->assertSame('apwc_retrieve', $d->id);
-        $this->assertInstanceOf('Stripe\\ApplePayDomain', $d);
-    }
-
-    public function testDeletion()
-    {
-        self::authorizeFromEnv();
-        $d = ApplePayDomain::create(array(
-            'domain_name' => 'jackshack.website'
-        ));
-        $this->assertInstanceOf('Stripe\\ApplePayDomain', $d);
-        $this->mockRequest(
-            'DELETE',
-            '/v1/apple_pay/domains/' . $d->id,
-            array(),
-            array('deleted' => true)
-        );
-        $d->delete();
-        $this->assertTrue($d->deleted);
-    }
-
-    public function testList()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/apple_pay/domains',
-            array(),
-            array(
-                'url' => '/v1/apple_pay/domains',
-                'object' => 'list'
-            )
-        );
-        $all = ApplePayDomain::all();
-        $this->assertSame($all->url, '/v1/apple_pay/domains');
-    }
-}
diff --git a/tests/ApplicationFeeRefundTest.php b/tests/ApplicationFeeRefundTest.php
deleted file mode 100644
index b8d266ad1..000000000
--- a/tests/ApplicationFeeRefundTest.php
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ApplicationFeeRefundTest extends TestCase
-{
-    public function testUrls()
-    {
-        $refund = new ApplicationFeeRefund();
-        $refund->id = 'refund_id';
-        $refund->fee = 'fee_id';
-
-        $this->assertSame(
-            $refund->instanceUrl(),
-            '/v1/application_fees/fee_id/refunds/refund_id'
-        );
-    }
-}
diff --git a/tests/ApplicationFeeTest.php b/tests/ApplicationFeeTest.php
deleted file mode 100644
index 6894fa8c8..000000000
--- a/tests/ApplicationFeeTest.php
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ApplicationFeeTest extends TestCase
-{
-    public function testUrls()
-    {
-        $applicationFee = new ApplicationFee('abcd/efgh');
-        $this->assertSame(
-            $applicationFee->instanceUrl(),
-            '/v1/application_fees/abcd%2Fefgh'
-        );
-    }
-
-    public function testList()
-    {
-        self::authorizeFromEnv();
-        $d = ApplicationFee::all();
-        $this->assertSame($d->url, '/v1/application_fees');
-    }
-
-    public function testStaticCreateRefund()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/application_fees/fee_123/refunds',
-            array(),
-            array('id' => 'fr_123', 'object' => 'fee_refund')
-        );
-
-        $feeRefund = ApplicationFee::createRefund(
-            'fee_123'
-        );
-
-        $this->assertSame('fr_123', $feeRefund->id);
-        $this->assertSame('fee_refund', $feeRefund->object);
-    }
-
-    public function testStaticRetrieveRefund()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/application_fees/fee_123/refunds/fr_123',
-            array(),
-            array('id' => 'fr_123', 'object' => 'fee_refund')
-        );
-
-        $feeRefund = ApplicationFee::retrieveRefund(
-            'fee_123',
-            'fr_123'
-        );
-
-        $this->assertSame('fr_123', $feeRefund->id);
-        $this->assertSame('fee_refund', $feeRefund->object);
-    }
-
-    public function testStaticUpdateRefund()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/application_fees/fee_123/refunds/fr_123',
-            array('metadata' => array('foo' => 'bar')),
-            array('id' => 'fr_123', 'object' => 'fee_refund')
-        );
-
-        $feeRefund = ApplicationFee::updateRefund(
-            'fee_123',
-            'fr_123',
-            array('metadata' => array('foo' => 'bar'))
-        );
-
-        $this->assertSame('fr_123', $feeRefund->id);
-        $this->assertSame('fee_refund', $feeRefund->object);
-    }
-
-    public function testStaticAllRefunds()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/application_fees/fee_123/refunds',
-            array(),
-            array('object' => 'list', 'data' => array())
-        );
-
-        $feeRefunds = ApplicationFee::allRefunds(
-            'fee_123'
-        );
-
-        $this->assertSame('list', $feeRefunds->object);
-        $this->assertEmpty($feeRefunds->data);
-    }
-}
diff --git a/tests/AuthenticationErrorTest.php b/tests/AuthenticationErrorTest.php
deleted file mode 100644
index 1003d6990..000000000
--- a/tests/AuthenticationErrorTest.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class AuthenticationErrorTest extends TestCase
-{
-    public function testInvalidCredentials()
-    {
-        Stripe::setApiKey('invalid');
-        try {
-            Customer::create();
-        } catch (Error\Authentication $e) {
-            $this->assertSame(401, $e->getHttpStatus());
-        }
-    }
-}
diff --git a/tests/BalanceTest.php b/tests/BalanceTest.php
deleted file mode 100644
index 3d6a2a668..000000000
--- a/tests/BalanceTest.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class BalanceTest extends TestCase
-{
-    public function testRetrieve()
-    {
-        self::authorizeFromEnv();
-        $d = Balance::retrieve();
-        $this->assertSame($d->object, "balance");
-        $this->assertTrue(Util\Util::isList($d->available));
-        $this->assertTrue(Util\Util::isList($d->pending));
-    }
-}
diff --git a/tests/BalanceTransactionTest.php b/tests/BalanceTransactionTest.php
deleted file mode 100644
index b196a2880..000000000
--- a/tests/BalanceTransactionTest.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class BalanceTransactionTest extends TestCase
-{
-    public function testList()
-    {
-        self::authorizeFromEnv();
-        $d = BalanceTransaction::all();
-        $this->assertSame($d->url, '/v1/balance/history');
-    }
-}
diff --git a/tests/BankAccountTest.php b/tests/BankAccountTest.php
deleted file mode 100644
index 285078f00..000000000
--- a/tests/BankAccountTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class BankAccountTest extends TestCase
-{
-    public function testVerify()
-    {
-        self::authorizeFromEnv();
-
-        $customer = self::createTestCustomer();
-
-        $bankAccount = $customer->sources->create(array(
-            'source' => array(
-                'object' => 'bank_account',
-                'account_holder_type' => 'individual',
-                'account_number' => '000123456789',
-                'account_holder_name' => 'John Doe',
-                'routing_number' => '110000000',
-                'country' => 'US'
-            )
-        ));
-
-        $this->assertSame($bankAccount->status, 'new');
-
-        $bankAccount = $bankAccount->verify(array(
-            'amounts' => array(32, 45)
-        ));
-
-        $this->assertSame($bankAccount->status, 'verified');
-    }
-}
diff --git a/tests/CardErrorTest.php b/tests/CardErrorTest.php
deleted file mode 100644
index 62a8bf1ca..000000000
--- a/tests/CardErrorTest.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class CardErrorTest extends TestCase
-{
-    public function testDecline()
-    {
-        self::authorizeFromEnv();
-
-        $charge = array(
-            'amount' => 100,
-            'currency' => 'usd',
-            'source' => 'tok_chargeDeclined'
-        );
-
-        try {
-            Charge::create($charge);
-        } catch (Error\Card $e) {
-            $this->assertSame(402, $e->getHttpStatus());
-            $this->assertTrue(strpos($e->getRequestId(), "req_") === 0, $e->getRequestId());
-            $actual = $e->getJsonBody();
-            $this->assertSame(
-                array('error' => array(
-                    'message' => 'Your card was declined.',
-                    'type' => 'card_error',
-                    'code' => 'card_declined',
-                    'decline_code' => 'generic_decline',
-                    'charge' => $actual['error']['charge'],
-                )),
-                $actual
-            );
-        }
-    }
-}
diff --git a/tests/ChargeTest.php b/tests/ChargeTest.php
deleted file mode 100644
index a5b8a4049..000000000
--- a/tests/ChargeTest.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ChargeTest extends TestCase
-{
-    public function testUrls()
-    {
-        $this->assertSame(Charge::classUrl(), '/v1/charges');
-        $charge = new Charge('abcd/efgh');
-        $this->assertSame($charge->instanceUrl(), '/v1/charges/abcd%2Fefgh');
-    }
-
-    public function testCreate()
-    {
-        self::authorizeFromEnv();
-
-        $c = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            )
-        );
-        $this->assertTrue($c->paid);
-        $this->assertFalse($c->refunded);
-    }
-
-    public function testIdempotentCreate()
-    {
-        self::authorizeFromEnv();
-
-        $c = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            ),
-            array(
-                'idempotency_key' => self::generateRandomString(),
-            )
-        );
-
-        $this->assertTrue($c->paid);
-        $this->assertSame(200, $c->getLastResponse()->code);
-    }
-
-    public function testRetrieve()
-    {
-        self::authorizeFromEnv();
-
-        $c = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            )
-        );
-        $d = Charge::retrieve($c->id);
-        $this->assertSame(200, $d->getLastResponse()->code);
-        $this->assertSame($d->id, $c->id);
-    }
-
-    public function testUpdateMetadata()
-    {
-        self::authorizeFromEnv();
-
-        $charge = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            )
-        );
-
-        $charge->metadata['test'] = 'foo bar';
-        $charge->save();
-
-        $updatedCharge = Charge::retrieve($charge->id);
-        $this->assertSame('foo bar', $updatedCharge->metadata['test']);
-    }
-
-    public function testUpdateMetadataAll()
-    {
-        self::authorizeFromEnv();
-
-        $charge = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            )
-        );
-
-        $charge->metadata = array('test' => 'foo bar');
-        $charge->save();
-        $this->assertSame(200, $charge->getLastResponse()->code);
-
-        $updatedCharge = Charge::retrieve($charge->id);
-        $this->assertSame('foo bar', $updatedCharge->metadata['test']);
-    }
-
-    public function testMarkAsFraudulent()
-    {
-        self::authorizeFromEnv();
-
-        $charge = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            )
-        );
-
-        $charge->refunds->create();
-        $charge->markAsFraudulent();
-
-        $updatedCharge = Charge::retrieve($charge->id);
-        $this->assertSame(
-            'fraudulent',
-            $updatedCharge['fraud_details']['user_report']
-        );
-    }
-
-    public function markAsSafe()
-    {
-        self::authorizeFromEnv();
-
-        $charge = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'card' => 'tok_visa'
-            )
-        );
-
-        $charge->markAsSafe();
-
-        $updatedCharge = Charge::retrieve($charge->id);
-        $this->assertSame('safe', $updatedCharge['fraud_details']['user_report']);
-    }
-}
diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php
deleted file mode 100644
index f8f923ff0..000000000
--- a/tests/CollectionTest.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class CollectionTest extends TestCase
-{
-    private function pageableModelResponse($ids, $hasMore)
-    {
-        $data = array();
-        foreach ($ids as $id) {
-            array_push($data, array(
-                'id' => $id,
-                'object' => 'pageablemodel'
-            ));
-        }
-        return array(
-            'object' => 'list',
-            'url' => '/v1/pageablemodels',
-            'data' => $data,
-            'has_more' => $hasMore
-        );
-    }
-
-    public function testAutoPagingOnePage()
-    {
-        $collection = Collection::constructFrom(
-            $this->pageableModelResponse(array('pm_123', 'pm_124'), false),
-            new Util\RequestOptions()
-        );
-
-        $seen = array();
-        foreach ($collection->autoPagingIterator() as $item) {
-            array_push($seen, $item['id']);
-        }
-
-        $this->assertSame($seen, array('pm_123', 'pm_124'));
-    }
-
-    public function testAutoPagingThreePages()
-    {
-        $collection = Collection::constructFrom(
-            $this->pageableModelResponse(array('pm_123', 'pm_124'), true),
-            new Util\RequestOptions()
-        );
-        $collection->setRequestParams(array('foo' => 'bar'));
-
-        $this->mockRequest(
-            'GET',
-            '/v1/pageablemodels',
-            array(
-                  'foo' => 'bar',
-                  'starting_after' => 'pm_124'
-            ),
-            $this->pageableModelResponse(array('pm_125', 'pm_126'), true)
-        );
-        $this->mockRequest(
-            'GET',
-            '/v1/pageablemodels',
-            array(
-                  'foo' => 'bar',
-                  'starting_after' => 'pm_126'
-            ),
-            $this->pageableModelResponse(array('pm_127'), false)
-        );
-
-        $seen = array();
-        foreach ($collection->autoPagingIterator() as $item) {
-            array_push($seen, $item['id']);
-        }
-
-        $this->assertSame($seen, array('pm_123', 'pm_124', 'pm_125', 'pm_126', 'pm_127'));
-    }
-
-    public function testIteratorToArray()
-    {
-        $collection = Collection::constructFrom(
-            $this->pageableModelResponse(array('pm_123', 'pm_124'), true),
-            new Util\RequestOptions()
-        );
-
-        $this->mockRequest(
-            'GET',
-            '/v1/pageablemodels',
-            array(
-                  'starting_after' => 'pm_124'
-            ),
-            $this->pageableModelResponse(array('pm_125', 'pm_126'), true)
-        );
-        $this->mockRequest(
-            'GET',
-            '/v1/pageablemodels',
-            array(
-                  'starting_after' => 'pm_126'
-            ),
-            $this->pageableModelResponse(array('pm_127'), false)
-        );
-
-        $seen = array();
-        foreach (iterator_to_array($collection->autoPagingIterator()) as $item) {
-            array_push($seen, $item['id']);
-        }
-
-        $this->assertSame($seen, array('pm_123', 'pm_124', 'pm_125', 'pm_126', 'pm_127'));
-    }
-}
diff --git a/tests/CountrySpecTest.php b/tests/CountrySpecTest.php
deleted file mode 100644
index aa652f36c..000000000
--- a/tests/CountrySpecTest.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class CountrySpecTest extends TestCase
-{
-    public function testRetrieve()
-    {
-        self::authorizeFromEnv();
-
-        $country = "US";
-        $d = CountrySpec::retrieve($country);
-        $this->assertSame($d->object, "country_spec");
-        $this->assertSame($d->id, $country);
-        $this->assertGreaterThan(0, count($d->supported_payment_currencies));
-        $this->assertGreaterThan(0, count($d->supported_payment_methods));
-    }
-
-    public function testList()
-    {
-        self::authorizeFromEnv();
-
-        $d = CountrySpec::all();
-        $this->assertSame($d->object, "list");
-        $this->assertGreaterThan(0, count($d->data));
-        $this->assertSame($d->data[0]->object, "country_spec");
-        $this->assertInstanceOf("Stripe\\CountrySpec", $d->data[0]);
-    }
-}
diff --git a/tests/CouponTest.php b/tests/CouponTest.php
deleted file mode 100644
index d23163e33..000000000
--- a/tests/CouponTest.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class CouponTest extends TestCase
-{
-    public function testSave()
-    {
-        self::authorizeFromEnv();
-        $id = 'test_coupon-' . self::generateRandomString(20);
-        $c = Coupon::create(
-            array(
-                'percent_off' => 25,
-                'duration' => 'repeating',
-                'duration_in_months' => 5,
-                'id' => $id,
-            )
-        );
-        $this->assertSame($id, $c->id);
-        // @codingStandardsIgnoreStart
-        $this->assertSame(25, $c->percent_off);
-        // @codingStandardsIgnoreEnd
-        $c->metadata['foo'] = 'bar';
-        $c->save();
-
-        $stripeCoupon = Coupon::retrieve($id);
-        $this->assertEquals($c->metadata, $stripeCoupon->metadata);
-    }
-}
diff --git a/tests/CustomerTest.php b/tests/CustomerTest.php
deleted file mode 100644
index bcee4e882..000000000
--- a/tests/CustomerTest.php
+++ /dev/null
@@ -1,338 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class CustomerTest extends TestCase
-{
-    public function testDeletion()
-    {
-        $customer = self::createTestCustomer();
-        $customer->delete();
-
-        $this->assertTrue($customer->deleted);
-        $this->assertNull($customer['active_card']);
-    }
-
-    public function testSave()
-    {
-        $customer = self::createTestCustomer();
-
-        $customer->email = 'gdb@stripe.com';
-        $customer->save();
-        $this->assertSame($customer->email, 'gdb@stripe.com');
-
-        $stripeCustomer = Customer::retrieve($customer->id);
-        $this->assertSame($customer->email, $stripeCustomer->email);
-
-        Stripe::setApiKey(null);
-        $customer = Customer::create(null, self::API_KEY);
-        $customer->email = 'gdb@stripe.com';
-        $customer->save();
-
-        self::authorizeFromEnv();
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $this->assertSame($updatedCustomer->email, 'gdb@stripe.com');
-    }
-
-    /**
-     * @expectedException Stripe\Error\InvalidRequest
-     */
-    public function testBogusAttribute()
-    {
-        $customer = self::createTestCustomer();
-        $customer->bogus = 'bogus';
-        $customer->save();
-    }
-
-    /**
-     * @expectedException InvalidArgumentException
-     */
-    public function testUpdateDescriptionEmpty()
-    {
-        $customer = self::createTestCustomer();
-        $customer->description = '';
-    }
-
-    public function testUpdateDescriptionNull()
-    {
-        $customer = self::createTestCustomer(array('description' => 'foo bar'));
-        $customer->description = null;
-
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $this->assertSame(null, $updatedCustomer->description);
-    }
-
-    public function testUpdateMetadata()
-    {
-        $customer = self::createTestCustomer();
-
-        $customer->metadata['test1'] = 'foo';
-        $customer->metadata['test2'] = 'bar';
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $this->assertSame(2, count($updatedCustomer->metadata));
-        $this->assertSame('foo', $updatedCustomer->metadata['test1']);
-        $this->assertSame('bar', $updatedCustomer->metadata['test2']);
-    }
-
-    public function testDeleteMetadata()
-    {
-        $customer = self::createTestCustomer();
-
-        $customer->metadata = null;
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $this->assertSame(0, count($updatedCustomer->metadata));
-    }
-
-    public function testUpdateSomeMetadata()
-    {
-        $customer = self::createTestCustomer();
-        $customer->metadata['shoe size'] = '7';
-        $customer->metadata['shirt size'] = 'XS';
-        $customer->save();
-
-        $customer->metadata['shoe size'] = '9';
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $this->assertSame('XS', $updatedCustomer->metadata['shirt size']);
-        $this->assertSame('9', $updatedCustomer->metadata['shoe size']);
-    }
-
-    public function testUpdateAllMetadata()
-    {
-        $customer = self::createTestCustomer();
-        $customer->metadata['shoe size'] = '7';
-        $customer->metadata['shirt size'] = 'XS';
-        $customer->save();
-
-        $customer->metadata = array('shirt size' => 'XL');
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $this->assertSame('XL', $updatedCustomer->metadata['shirt size']);
-        $this->assertFalse(isset($updatedCustomer->metadata['shoe size']));
-    }
-
-    /**
-     * @expectedException Stripe\Error\InvalidRequest
-     */
-    public function testUpdateInvalidMetadata()
-    {
-        $customer = self::createTestCustomer();
-        $customer->metadata = 'something';
-        $customer->save();
-    }
-
-    public function testCancelSubscription()
-    {
-        $planID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($planID);
-
-        $customer = self::createTestCustomer(
-            array(
-                'plan' => $planID,
-            )
-        );
-
-        $customer->cancelSubscription(array('at_period_end' => true));
-        $this->assertSame($customer->subscription->status, 'active');
-        $this->assertTrue($customer->subscription->cancel_at_period_end);
-        $customer->cancelSubscription();
-        $this->assertSame($customer->subscription->status, 'canceled');
-    }
-
-    public function testCustomerAddCard()
-    {
-        $customer = $this->createTestCustomer();
-        $createdCard = $customer->sources->create(array("card" => 'tok_visa'));
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $updatedCards = $updatedCustomer->sources->all();
-        $this->assertSame(count($updatedCards["data"]), 2);
-    }
-
-    public function testCustomerUpdateCard()
-    {
-        $customer = $this->createTestCustomer();
-        $customer->save();
-
-        $sources = $customer->sources->all();
-        $this->assertSame(count($sources["data"]), 1);
-
-        $card = $sources['data'][0];
-        $card->name = "Jane Austen";
-        $card->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $updatedCards = $updatedCustomer->sources->all();
-        $this->assertSame($updatedCards["data"][0]->name, "Jane Austen");
-    }
-
-    public function testCustomerDeleteCard()
-    {
-        $customer = $this->createTestCustomer();
-        $createdCard = $customer->sources->create(array("card" => 'tok_visa'));
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $updatedCards = $updatedCustomer->sources->all();
-        $this->assertSame(count($updatedCards["data"]), 2);
-
-        $deleteStatus = $updatedCustomer->sources->retrieve($createdCard->id)->delete();
-        $this->assertTrue($deleteStatus->deleted);
-        $updatedCustomer->save();
-
-        $postDeleteCustomer = Customer::retrieve($customer->id);
-        $postDeleteCards = $postDeleteCustomer->sources->all();
-        $this->assertSame(count($postDeleteCards["data"]), 1);
-    }
-
-    public function testCustomerAddSource()
-    {
-        self::authorizeFromEnv();
-
-        $customer = $this->createTestCustomer();
-        $createdSource = $customer->sources->create(array("source" => 'tok_visa'));
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $updatedSources = $updatedCustomer->sources->all();
-        $this->assertSame(count($updatedSources["data"]), 2);
-    }
-
-    public function testCustomerUpdateSource()
-    {
-        $customer = $this->createTestCustomer();
-        $customer->save();
-
-        $sources = $customer->sources->all();
-        $this->assertSame(count($sources["data"]), 1);
-
-        $source = $sources['data'][0];
-        $source->name = "Jane Austen";
-        $source->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $updatedSources = $updatedCustomer->sources->all();
-        $this->assertSame($updatedSources["data"][0]->name, "Jane Austen");
-    }
-
-    public function testCustomerDeleteSource()
-    {
-        self::authorizeFromEnv();
-
-        $customer = $this->createTestCustomer();
-        $createdSource = $customer->sources->create(array("source" => 'tok_visa'));
-        $customer->save();
-
-        $updatedCustomer = Customer::retrieve($customer->id);
-        $updatedSources = $updatedCustomer->sources->all();
-        $this->assertSame(count($updatedSources["data"]), 2);
-
-        $deleteStatus = $updatedCustomer->sources->retrieve($createdSource->id)->delete();
-        $this->assertTrue($deleteStatus->deleted);
-        $updatedCustomer->save();
-
-        $postDeleteCustomer = Customer::retrieve($customer->id);
-        $postDeleteSources = $postDeleteCustomer->sources->all();
-        $this->assertSame(count($postDeleteSources["data"]), 1);
-    }
-
-
-    public function testStaticCreateSource()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/customers/cus_123/sources',
-            array('source' => 'tok_123'),
-            array('id' => 'card_123', 'object' => 'card')
-        );
-
-        $source = Customer::createSource(
-            'cus_123',
-            array('source' => 'tok_123')
-        );
-
-        $this->assertSame('card_123', $source->id);
-        $this->assertSame('card', $source->object);
-    }
-
-    public function testStaticRetrieveSource()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/customers/cus_123/sources/card_123',
-            array(),
-            array('id' => 'card_123', 'object' => 'card')
-        );
-
-        $source = Customer::retrieveSource(
-            'cus_123',
-            'card_123'
-        );
-
-        $this->assertSame('card_123', $source->id);
-        $this->assertSame('card', $source->object);
-    }
-
-    public function testStaticUpdateSource()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/customers/cus_123/sources/card_123',
-            array('metadata' => array('foo' => 'bar')),
-            array('id' => 'card_123', 'object' => 'card')
-        );
-
-        $source = Customer::updateSource(
-            'cus_123',
-            'card_123',
-            array('metadata' => array('foo' => 'bar'))
-        );
-
-        $this->assertSame('card_123', $source->id);
-        $this->assertSame('card', $source->object);
-    }
-
-    public function testStaticDeleteSource()
-    {
-        $this->mockRequest(
-            'DELETE',
-            '/v1/customers/cus_123/sources/card_123',
-            array(),
-            array('id' => 'card_123', 'deleted' => true)
-        );
-
-        $source = Customer::deleteSource(
-            'cus_123',
-            'card_123'
-        );
-
-        $this->assertSame('card_123', $source->id);
-        $this->assertSame(true, $source->deleted);
-    }
-
-    public function testStaticAllSources()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/customers/cus_123/sources',
-            array(),
-            array('object' => 'list', 'data' => array())
-        );
-
-        $sources = Customer::allsources(
-            'cus_123'
-        );
-
-        $this->assertSame('list', $sources->object);
-        $this->assertEmpty($sources->data);
-    }
-}
diff --git a/tests/DiscountTest.php b/tests/DiscountTest.php
deleted file mode 100644
index 1e77029ad..000000000
--- a/tests/DiscountTest.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class DiscountTest extends TestCase
-{
-    public function testDeletion()
-    {
-        self::authorizeFromEnv();
-        $id = 'test-coupon-' . self::generateRandomString(20);
-        $coupon = Coupon::create(
-            array(
-                'percent_off' => 25,
-                'duration' => 'repeating',
-                'duration_in_months' => 5,
-                'id' => $id,
-            )
-        );
-        $customer = self::createTestCustomer(array('coupon' => $id));
-
-        $this->assertTrue(isset($customer->discount));
-        $this->assertTrue(isset($customer->discount->coupon));
-        $this->assertSame($id, $customer->discount->coupon->id);
-
-        $customer->deleteDiscount();
-        $this->assertFalse(isset($customer->discount));
-
-        $customer = Customer::retrieve($customer->id);
-        $this->assertFalse(isset($customer->discount));
-    }
-}
diff --git a/tests/DisputeTest.php b/tests/DisputeTest.php
deleted file mode 100644
index bac033c1b..000000000
--- a/tests/DisputeTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class DisputeTest extends TestCase
-{
-    public function testUrls()
-    {
-        $this->assertSame(Dispute::classUrl(), '/v1/disputes');
-        $dispute = new Dispute('dp_123');
-        $this->assertSame($dispute->instanceUrl(), '/v1/disputes/dp_123');
-    }
-
-    private function createDisputedCharge()
-    {
-        $c = Charge::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'source' => 'tok_createDispute'
-            )
-        );
-        $c = Charge::retrieve($c->id);
-
-        $attempts = 0;
-
-        while ($c->dispute === null) {
-            if ($attempts > 5) {
-                throw new \Exception("Charge is taking too long to be disputed");
-            }
-            sleep(1);
-            $c = Charge::retrieve($c->id);
-            $attempts += 1;
-        }
-
-        return $c;
-    }
-
-    public function testAll()
-    {
-        self::authorizeFromEnv();
-
-        $sublist = Dispute::all(
-            array(
-                'limit' => 3,
-            )
-        );
-        $this->assertSame(3, count($sublist->data));
-    }
-
-
-    public function testUpdate()
-    {
-        self::authorizeFromEnv();
-
-        $c = $this->createDisputedCharge();
-
-        $d = Dispute::retrieve($c->dispute);
-        $d->evidence["customer_name"] = "Bob";
-        $s = $d->save();
-
-        $this->assertSame($c->dispute, $s->id);
-        $this->assertSame("Bob", $s->evidence["customer_name"]);
-    }
-
-    public function testClose()
-    {
-        self::authorizeFromEnv();
-
-        $c = $this->createDisputedCharge();
-        $d = Dispute::retrieve($c->dispute);
-
-        $this->assertNotSame("lost", $d->status);
-
-        $d->close();
-
-        $this->assertSame("lost", $d->status);
-    }
-
-    public function testRetrieve()
-    {
-        self::authorizeFromEnv();
-
-        $c = $this->createDisputedCharge();
-
-        $d = Dispute::retrieve($c->dispute);
-
-        $this->assertSame($c->dispute, $d->id);
-    }
-}
diff --git a/tests/EphemeralKeyTest.php b/tests/EphemeralKeyTest.php
deleted file mode 100644
index cf608b1bd..000000000
--- a/tests/EphemeralKeyTest.php
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class EphemeralKeyTest extends TestCase
-{
-    private $oldApiVersion = null;
-
-    /**
-     * @before
-     */
-    public function setUpApiVersion()
-    {
-        $this->oldApiVersion = Stripe::getApiVersion();
-    }
-
-    /**
-     * @after
-     */
-    public function tearDownApiVersion()
-    {
-        Stripe::setApiVersion($this->oldApiVersion);
-    }
-
-    public function testVersionlessCreateWithoutGlobalVersion()
-    {
-        $this->setExpectedException('\InvalidArgumentException');
-        $key = EphemeralKey::create(
-            array('customer' => 'cus_123')
-        );
-    }
-
-    public function testVersionedCreateWithoutGlobalVersion()
-    {
-        $response = $this->ephemeralKeyResponse('cus_123');
-        $this->mockCreate($response);
-
-        $key = EphemeralKey::create(
-            array('customer' => 'cus_123'),
-            array('stripe_version' => '2017-05-25')
-        );
-        $this->assertSame($key->id, $response['id']);
-    }
-
-    public function testVersionlessCreateWithGlobalVersion()
-    {
-        Stripe::setApiVersion('2017-06-05');
-        $this->setExpectedException('\InvalidArgumentException');
-        $key = EphemeralKey::create(
-            array('customer' => 'cus_123')
-        );
-    }
-
-    public function testVersionedCreateWithGlobalVersion()
-    {
-        Stripe::setApiVersion('2017-06-05');
-        $response = $this->ephemeralKeyResponse('cus_123');
-        $this->mockCreate($response);
-
-        $key = EphemeralKey::create(
-            array('customer' => 'cus_123'),
-            array('stripe_version' => '2017-05-25')
-        );
-        $this->assertSame($key->id, $response['id']);
-    }
-
-    public function testDelete()
-    {
-        $response = $this->ephemeralKeyResponse('cus_123');
-        $this->mockCreate($response);
-        $this->mockDelete($response);
-
-        $key = EphemeralKey::create(
-            array('customer' => 'cus_123'),
-            array('stripe_version' => '2017-05-25')
-        );
-
-        $deleted = $key->delete();
-        $this->assertSame($key->id, $deleted->id);
-    }
-
-    protected function mockCreate($response)
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/ephemeral_keys',
-            array('customer' => $response['associated_objects'][0]['id']),
-            $response
-        );
-    }
-
-    protected function mockDelete($response)
-    {
-        $this->mockRequest(
-            'DELETE',
-            sprintf('/v1/ephemeral_keys/%s', $response['id']),
-            array(),
-            $response
-        );
-    }
-
-    protected function ephemeralKeyResponse($customer)
-    {
-        return array(
-            'id' => 'ephkey_123',
-            'object' => 'ephemeral_key',
-            'associated_objects' => array(array(
-                'type' => 'customer',
-                'id' => $customer
-            )),
-            'created' => 1496957039,
-            'expires' => 1496960639,
-            'livemode' => false,
-            'secret' => 'ek_test_supersecretstring'
-        );
-    }
-}
diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php
deleted file mode 100644
index 753624275..000000000
--- a/tests/ErrorTest.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ErrorTest extends TestCase
-{
-    public function testCreation()
-    {
-        try {
-            throw new Error\Api(
-                "hello",
-                500,
-                "{'foo':'bar'}",
-                array('foo' => 'bar')
-            );
-            $this->fail("Did not raise error");
-        } catch (Error\Api $e) {
-            $this->assertSame("hello", $e->getMessage());
-            $this->assertSame(500, $e->getHttpStatus());
-            $this->assertSame("{'foo':'bar'}", $e->getHttpBody());
-            $this->assertSame(array('foo' => 'bar'), $e->getJsonBody());
-            $this->assertSame(null, $e->getHttpHeaders());
-            $this->assertSame(null, $e->getRequestId());
-        }
-    }
-
-    public function testResponseHeaders()
-    {
-        try {
-            throw new Error\Api(
-                "hello",
-                500,
-                "{'foo':'bar'}",
-                array('foo' => 'bar'),
-                array('Request-Id' => 'req_bar')
-            );
-            $this->fail("Did not raise error");
-        } catch (Error\Api $e) {
-            $this->assertSame(array('Request-Id' => 'req_bar'), $e->getHttpHeaders());
-            $this->assertSame('req_bar', $e->getRequestId());
-        }
-    }
-
-    public function testCode()
-    {
-        try {
-            throw new Error\Card(
-                "hello",
-                "some_param",
-                "some_code",
-                400,
-                "{'foo':'bar'}",
-                array('foo' => 'bar')
-            );
-            $this->fail("Did not raise error");
-        } catch (Error\Card $e) {
-            $this->assertSame("some_param", $e->getStripeParam());
-            $this->assertSame('some_code', $e->getStripeCode());
-        }
-    }
-}
diff --git a/tests/ExternalAccountTest.php b/tests/ExternalAccountTest.php
deleted file mode 100644
index 70c0068bc..000000000
--- a/tests/ExternalAccountTest.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ExternalAccountTest extends TestCase
-{
-    public function testVerify()
-    {
-        self::authorizeFromEnv();
-        $bankAccountToken = Token::create(
-            array(
-                'bank_account' => array(
-                'country' => 'US',
-                'routing_number' => '110000000',
-                'account_number' => '000123456789',
-                'account_holder_name' => 'Jane Austen',
-                'account_holder_type' => 'company'
-                )
-            )
-        );
-        $customer = Customer::create();
-        $externalAccount = $customer->sources->create(array('bank_account' => $bankAccountToken->id));
-        $verifiedAccount = $externalAccount->verify(array('amounts' => array(32, 45)), null);
-
-        $base = Customer::classUrl();
-        $parentExtn = $externalAccount['customer'];
-        $extn = $externalAccount['id'];
-        $this->assertEquals("$base/$parentExtn/sources/$extn", $externalAccount->instanceUrl());
-    }
-}
diff --git a/tests/FileUploadTest.php b/tests/FileUploadTest.php
deleted file mode 100644
index 5ed3ba574..000000000
--- a/tests/FileUploadTest.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class FileUploadTest extends TestCase
-{
-    public function testCreateFile()
-    {
-        $fp = fopen(dirname(__FILE__).'/../data/test.png', 'r');
-        self::authorizeFromEnv();
-        $file = FileUpload::create(
-            array(
-                'purpose' => 'dispute_evidence',
-                'file' => $fp,
-            )
-        );
-        fclose($fp);
-        $this->assertSame(95, $file->size);
-        $this->assertSame('png', $file->type);
-        $this->assertInstanceOf('Stripe\\FileUpload', $file);
-    }
-
-    public function testCreateAndRetrieveCurlFile()
-    {
-        if (!class_exists('\CurlFile', false)) {
-            // Older PHP versions don't support this
-            return;
-        }
-
-        $curlFile = new \CurlFile(dirname(__FILE__).'/../data/test.png');
-        self::authorizeFromEnv();
-        $file = FileUpload::create(
-            array(
-                'purpose' => 'dispute_evidence',
-                'file' => $curlFile,
-            )
-        );
-        $this->assertSame(95, $file->size);
-        $this->assertSame('png', $file->type);
-
-        // Just check that we don't get exceptions
-        $file = FileUpload::retrieve($file->id);
-        $file->refresh();
-    }
-}
diff --git a/tests/InvalidRequestErrorTest.php b/tests/InvalidRequestErrorTest.php
deleted file mode 100644
index 6cc6d6829..000000000
--- a/tests/InvalidRequestErrorTest.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class InvalidRequestErrorTest extends TestCase
-{
-    public function testInvalidObject()
-    {
-        self::authorizeFromEnv();
-        try {
-            Customer::retrieve('invalid');
-        } catch (Error\InvalidRequest $e) {
-            $this->assertSame(404, $e->getHttpStatus());
-        }
-    }
-
-    public function testBadData()
-    {
-        self::authorizeFromEnv();
-        try {
-            Charge::create();
-        } catch (Error\InvalidRequest $e) {
-            $this->assertSame(400, $e->getHttpStatus());
-        }
-    }
-}
diff --git a/tests/InvoiceTest.php b/tests/InvoiceTest.php
deleted file mode 100644
index 1ba93c905..000000000
--- a/tests/InvoiceTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class InvoiceTest extends TestCase
-{
-    public function testUpcoming()
-    {
-        self::authorizeFromEnv();
-        $customer = self::createTestCustomer();
-
-        InvoiceItem::create(array(
-            'customer'  => $customer->id,
-            'amount'    => 0,
-            'currency'  => 'usd',
-        ));
-
-        $invoice = Invoice::upcoming(array(
-            'customer' => $customer->id,
-        ));
-        $this->assertSame($invoice->customer, $customer->id);
-        $this->assertSame($invoice->attempted, false);
-    }
-
-    public function testItemsAccessWithParameter()
-    {
-        self::authorizeFromEnv();
-        $customer = self::createTestCustomer();
-
-        InvoiceItem::create(array(
-            'customer'  => $customer->id,
-            'amount'    => 100,
-            'currency'  => 'usd',
-        ));
-
-        $invoice = Invoice::upcoming(
-            array(
-            'customer' => $customer->id,
-            )
-        );
-
-        $lines = $invoice->lines->all(array('limit' => 10));
-
-        $this->assertSame(count($lines->data), 1);
-        $this->assertSame($lines->data[0]->amount, 100);
-    }
-
-    // This is really just making sure that this operation does not trigger any
-    // warnings, as it's highly nested.
-    public function testAll()
-    {
-        self::authorizeFromEnv();
-        $invoices = Invoice::all();
-        $this->assertGreaterThan(0, count($invoices->data));
-    }
-
-    public function testPay()
-    {
-        $response = array(
-            'id' => 'in_foo',
-            'object' => 'invoice',
-            'paid' => false,
-        );
-        $this->mockRequest(
-            'GET',
-            '/v1/invoices/in_foo',
-            array(),
-            $response
-        );
-
-        $response['paid'] = true;
-        $this->mockRequest(
-            'POST',
-            '/v1/invoices/in_foo/pay',
-            array('source' => 'src_bar'),
-            $response
-        );
-
-        $invoice = Invoice::retrieve('in_foo');
-        $invoice->pay(array('source' => 'src_bar'));
-        $this->assertTrue($invoice->paid);
-    }
-}
diff --git a/tests/PayoutTest.php b/tests/PayoutTest.php
deleted file mode 100644
index a8a0b240d..000000000
--- a/tests/PayoutTest.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class PayoutTest extends TestCase
-{
-    private $managedAccount = null;
-
-    /**
-     * Create a managed account and put enough funds in the balance
-     * to be able to create a payout afterwards. Also try to re-use
-     * the managed account across the tests to avoid hitting the
-     * rate limit for account creation.
-     */
-    private function createAccountWithBalance()
-    {
-        if ($this->managedAccount === null) {
-            self::authorizeFromEnv();
-            $account = self::createTestManagedAccount();
-
-            $charge = \Stripe\Charge::create(array(
-                'currency' => 'usd',
-                'amount' => '10000',
-                'source' => 'tok_bypassPending',
-                'destination' => array(
-                    'account' => $account->id
-                )
-            ));
-
-            $this->managedAccount = $account;
-        }
-
-        return $this->managedAccount;
-    }
-
-    private function createPayoutFromManagedAccount($accountId)
-    {
-        $payout = Payout::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-            ),
-            array(
-                'stripe_account' => $accountId
-            )
-        );
-
-        return $payout;
-    }
-
-    public function testCreate()
-    {
-        $account = self::createAccountWithBalance();
-        $payout =  self::createPayoutFromManagedAccount($account->id);
-
-        $this->assertSame('pending', $payout->status);
-    }
-
-    public function testRetrieve()
-    {
-        $account = self::createAccountWithBalance();
-        $payout =  self::createPayoutFromManagedAccount($account->id);
-        $reloaded = Payout::retrieve($payout->id, array('stripe_account' => $account->id));
-        $this->assertSame($reloaded->id, $payout->id);
-    }
-
-    public function testPayoutUpdateMetadata()
-    {
-        $account = self::createAccountWithBalance();
-        $payout =  self::createPayoutFromManagedAccount($account->id);
-        $payout->metadata['test'] = 'foo bar';
-        $payout->save();
-
-        $updatedPayout = Payout::retrieve($payout->id, array('stripe_account' => $account->id));
-        $this->assertSame('foo bar', $updatedPayout->metadata['test']);
-    }
-
-    public function testPayoutUpdateMetadataAll()
-    {
-        $account = self::createAccountWithBalance();
-        $payout =  self::createPayoutFromManagedAccount($account->id);
-
-        $payout->metadata = array('test' => 'foo bar');
-        $payout->save();
-
-        $updatedPayout = Payout::retrieve($payout->id, array('stripe_account' => $account->id));
-        $this->assertSame('foo bar', $updatedPayout->metadata['test']);
-    }
-}
diff --git a/tests/PermissionsErrorTest.php b/tests/PermissionsErrorTest.php
deleted file mode 100644
index cc8a5cd0b..000000000
--- a/tests/PermissionsErrorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class PermissionErrorTest extends TestCase
-{
-    private function permissionErrorResponse()
-    {
-        return array(
-            'error' => array(),
-        );
-    }
-
-    /**
-     * @expectedException Stripe\Error\Permission
-     */
-    public function testPermission()
-    {
-        $this->mockRequest('GET', '/v1/accounts/acct_DEF', array(), $this->permissionErrorResponse(), 403);
-        Account::retrieve('acct_DEF');
-    }
-}
diff --git a/tests/PlanTest.php b/tests/PlanTest.php
deleted file mode 100644
index 9aa3589c7..000000000
--- a/tests/PlanTest.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class PlanTest extends TestCase
-{
-    public function testDeletion()
-    {
-        self::authorizeFromEnv();
-        $p = Plan::create(array(
-            'amount' => 2000,
-            'interval' => 'month',
-            'currency' => 'usd',
-            'name' => 'Plan',
-            'id' => 'gold-' . self::generateRandomString(20)
-        ));
-        $p->delete();
-        $this->assertTrue($p->deleted);
-    }
-
-    public function testFalseyId()
-    {
-        try {
-            $retrievedPlan = Plan::retrieve('0');
-        } catch (Error\InvalidRequest $e) {
-            // Can either succeed or 404, all other errors are bad
-            if ($e->httpStatus !== 404) {
-                $this->fail();
-            }
-        }
-    }
-
-    public function testSave()
-    {
-        self::authorizeFromEnv();
-        $planID = 'gold-' . self::generateRandomString(20);
-        $p = Plan::create(array(
-            'amount'   => 2000,
-            'interval' => 'month',
-            'currency' => 'usd',
-            'name'     => 'Plan',
-            'id'       => $planID
-        ));
-        $p->name = 'A new plan name';
-        $p->save();
-        $this->assertSame($p->name, 'A new plan name');
-
-        $stripePlan = Plan::retrieve($planID);
-        $this->assertSame($p->name, $stripePlan->name);
-    }
-}
diff --git a/tests/ProductTest.php b/tests/ProductTest.php
deleted file mode 100644
index ea4c15a3e..000000000
--- a/tests/ProductTest.php
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ProductSKUOrderTest extends TestCase
-{
-    public function testProductFalseyId()
-    {
-        try {
-            Stripe::setApiKey('sk_test_JieJALRz7rPz7boV17oMma7a');
-            $retrievedProduct = Product::retrieve('0');
-        } catch (Error\InvalidRequest $e) {
-            // Can either succeed or 404, all other errors are bad
-            if ($e->httpStatus !== 404) {
-                $this->fail();
-            }
-        }
-    }
-
-    public function testProductCreateUpdateRead()
-    {
-
-        Stripe::setApiKey('sk_test_JieJALRz7rPz7boV17oMma7a');
-        $ProductID = 'gold-' . self::generateRandomString(20);
-        $p = Product::create(array(
-            'name'     => 'Gold Product',
-            'id'       => $ProductID,
-            'url'      => 'www.stripe.com/gold'
-        ));
-        $this->assertSame($p->url, 'www.stripe.com/gold');
-
-        $p->name = 'A new Product name';
-        $p->save();
-        $this->assertSame($p->name, 'A new Product name');
-        $this->assertSame($p->url, 'www.stripe.com/gold');
-
-        $stripeProduct = Product::retrieve($ProductID);
-        $this->assertSame($p->name, $stripeProduct->name);
-        $this->assertSame($stripeProduct->url, 'www.stripe.com/gold');
-    }
-
-    public function testSKUCreateUpdateRead()
-    {
-        Stripe::setApiKey('sk_test_JieJALRz7rPz7boV17oMma7a');
-        $ProductID = 'silver-' . self::generateRandomString(20);
-        $p = Product::create(array(
-            'name'     => 'Silver Product',
-            'id'       => $ProductID,
-            'url'      => 'www.stripe.com/silver'
-        ));
-
-        $SkuID = 'silver-sku-' . self::generateRandomString(20);
-        $sku = SKU::create(array(
-            'price'     => 500,
-            'currency'  => 'usd',
-            'id'        => $SkuID,
-            'inventory' => array(
-                'type'     => 'finite',
-                'quantity' => 40
-            ),
-            'product'   => $ProductID
-        ));
-
-        $sku->price = 600;
-        $sku->inventory->quantity = 50;
-        $sku->save();
-        $this->assertSame($sku->price, 600);
-        $this->assertSame(50, $sku->inventory->quantity);
-
-        $stripeSku = SKU::retrieve($SkuID);
-        $this->assertSame($sku->price, 600);
-        $this->assertSame('finite', $sku->inventory->type);
-        $this->assertSame(50, $sku->inventory->quantity);
-    }
-
-    public function testSKUProductDelete()
-    {
-        Stripe::setApiKey('sk_test_JieJALRz7rPz7boV17oMma7a');
-        $ProductID = 'silver-' . self::generateRandomString(20);
-        $p = Product::create(array(
-            'name'     => 'Silver Product',
-            'id'       => $ProductID,
-            'url'      => 'stripe.com/silver'
-        ));
-
-        $SkuID = 'silver-sku-' . self::generateRandomString(20);
-        $sku = SKU::create(array(
-            'price'     => 500,
-            'currency'  => 'usd',
-            'id'        => $SkuID,
-            'inventory' => array(
-                'type'     => 'finite',
-                'quantity' => 40
-            ),
-            'product'   => $ProductID
-        ));
-
-        $deletedSku = $sku->delete();
-        $this->assertTrue($deletedSku->deleted);
-
-        $deletedProduct = $p->delete();
-        $this->assertTrue($deletedProduct->deleted);
-    }
-
-    public function testOrderCreateUpdateRetrievePayReturn()
-    {
-        Stripe::setApiKey('sk_test_JieJALRz7rPz7boV17oMma7a');
-        $ProductID = 'silver-' . self::generateRandomString(20);
-        $p = Product::create(array(
-            'name'      => 'Silver Product',
-            'id'        => $ProductID,
-            'url'       => 'www.stripe.com/silver',
-            'shippable' => false,
-        ));
-
-        $SkuID = 'silver-sku-' . self::generateRandomString(20);
-        $sku = SKU::create(array(
-            'price'     => 500,
-            'currency'  => 'usd',
-            'id'        => $SkuID,
-            'inventory' => array(
-                'type'     => 'finite',
-                'quantity' => 40
-            ),
-            'product'   => $ProductID
-        ));
-
-        $order = Order::create(array(
-            'items' => array(
-                0 => array(
-                    'type' => 'sku',
-                    'parent' => $SkuID,
-                ),
-            ),
-            'currency' => 'usd',
-            'email' => 'foo@bar.com',
-        ));
-
-        $order->metadata->foo = "bar";
-        $order->save();
-
-        $stripeOrder = Order::retrieve($order->id);
-        $this->assertSame($order->metadata->foo, "bar");
-
-        $order->pay(array('source' => 'tok_visa'));
-        $this->assertSame($order->status, 'paid');
-
-        $orderReturn = $order->returnOrder();
-        $this->assertSame($orderReturn->order, $order->id);
-    }
-}
diff --git a/tests/RateLimitErrorTest.php b/tests/RateLimitErrorTest.php
deleted file mode 100644
index 2d6a6b22a..000000000
--- a/tests/RateLimitErrorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class RateLimitErrorTest extends TestCase
-{
-    private function rateLimitErrorResponse()
-    {
-        return array(
-            'error' => array(),
-        );
-    }
-
-    /**
-     * @expectedException Stripe\Error\RateLimit
-     */
-    public function testRateLimit()
-    {
-        $this->mockRequest('GET', '/v1/accounts/acct_DEF', array(), $this->rateLimitErrorResponse(), 429);
-        Account::retrieve('acct_DEF');
-    }
-}
diff --git a/tests/RecipientTest.php b/tests/RecipientTest.php
deleted file mode 100644
index 7a51646bd..000000000
--- a/tests/RecipientTest.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class RecipientTest extends TestCase
-{
-    public function testDeletion()
-    {
-        $recipient = self::createTestRecipient();
-        $recipient->delete();
-
-        $this->assertTrue($recipient->deleted);
-    }
-
-    public function testSave()
-    {
-        $recipient = self::createTestRecipient();
-
-        $recipient->email = 'gdb@stripe.com';
-        $recipient->save();
-        $this->assertSame($recipient->email, 'gdb@stripe.com');
-
-        $stripeRecipient = Recipient::retrieve($recipient->id);
-        $this->assertSame($recipient->email, $stripeRecipient->email);
-    }
-
-    /**
-     * @expectedException Stripe\Error\InvalidRequest
-     */
-    public function testBogusAttribute()
-    {
-        $recipient = self::createTestRecipient();
-        $recipient->bogus = 'bogus';
-        $recipient->save();
-    }
-
-    public function testRecipientAddCard()
-    {
-        $recipient = $this->createTestRecipient();
-        $createdCard = $recipient->cards->create(array("card" => 'tok_visa_debit'));
-        $recipient->save();
-
-        $updatedRecipient = Recipient::retrieve($recipient->id);
-        $updatedCards = $updatedRecipient->cards->all();
-        $this->assertSame(count($updatedCards["data"]), 1);
-    }
-
-    public function testRecipientUpdateCard()
-    {
-        $recipient = $this->createTestRecipient();
-        $createdCard = $recipient->cards->create(array("card" => 'tok_visa_debit'));
-        $recipient->save();
-
-        $createdCards = $recipient->cards->all();
-        $this->assertSame(count($createdCards["data"]), 1);
-
-        $card = $createdCards['data'][0];
-        $card->name = "Jane Austen";
-        $card->save();
-
-        $updatedRecipient = Recipient::retrieve($recipient->id);
-        $updatedCards = $updatedRecipient->cards->all();
-        $this->assertSame($updatedCards["data"][0]->name, "Jane Austen");
-    }
-
-    public function testRecipientDeleteCard()
-    {
-        $recipient = $this->createTestRecipient();
-        $createdCard = $recipient->cards->create(array("card" => 'tok_visa_debit'));
-        $recipient->save();
-
-        $updatedRecipient = Recipient::retrieve($recipient->id);
-        $updatedCards = $updatedRecipient->cards->all();
-        $this->assertSame(count($updatedCards["data"]), 1);
-
-        $deleteStatus =
-        $updatedRecipient->cards->retrieve($createdCard->id)->delete();
-        $this->assertTrue($deleteStatus->deleted);
-        $updatedRecipient->save();
-
-        $postDeleteRecipient = Recipient::retrieve($recipient->id);
-        $postDeleteCards = $postDeleteRecipient->cards->all();
-        $this->assertSame(count($postDeleteCards["data"]), 0);
-    }
-}
diff --git a/tests/RefundTest.php b/tests/RefundTest.php
deleted file mode 100644
index 87729eab8..000000000
--- a/tests/RefundTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class RefundTest extends TestCase
-{
-
-    public function testCreate()
-    {
-        $charge = self::createTestCharge();
-        $refund = Refund::create(array('amount' => 100, 'charge' => $charge->id));
-        $this->assertSame(100, $refund->amount);
-        $this->assertSame($charge->id, $refund->charge);
-    }
-
-    public function testUpdateAndRetrieve()
-    {
-        $charge = self::createTestCharge();
-        $ref = Refund::create(array('amount' => 100, 'charge' => $charge->id));
-        $ref->metadata["key"] = "value";
-        $ref->save();
-        $ref = Refund::retrieve($ref->id);
-        $this->assertSame("value", $ref->metadata["key"], "value");
-    }
-
-    public function testListForCharge()
-    {
-        $charge = self::createTestCharge();
-        $refA = Refund::create(array('amount' => 100, 'charge' => $charge->id));
-        $refB = Refund::create(array('amount' => 50, 'charge' => $charge->id));
-
-        $all = Refund::all(array('charge' => $charge));
-        $this->assertSame(false, $all['has_more']);
-        $this->assertSame(2, count($all->data));
-        $this->assertSame($refB->id, $all->data[0]->id);
-        $this->assertSame($refA->id, $all->data[1]->id);
-    }
-
-    public function testList()
-    {
-        $all = Refund::all();
-
-        // Fetches all refunds on this test account.
-        $this->assertSame(true, $all['has_more']);
-        $this->assertSame(10, count($all->data));
-    }
-
-    // Deprecated charge endpoints:
-
-    public function testCreateViaCharge()
-    {
-        $charge = self::createTestCharge();
-        $ref = $charge->refunds->create(array('amount' => 100));
-        $this->assertSame(100, $ref->amount);
-        $this->assertSame($charge->id, $ref->charge);
-    }
-
-    public function testUpdateAndRetrieveViaCharge()
-    {
-        $charge = self::createTestCharge();
-        $ref = $charge->refunds->create(array('amount' => 100));
-        $ref->metadata["key"] = "value";
-        $ref->save();
-        $ref = $charge->refunds->retrieve($ref->id);
-        $this->assertSame("value", $ref->metadata["key"], "value");
-    }
-
-    public function testListViaCharge()
-    {
-        $charge = self::createTestCharge();
-        $refA = $charge->refunds->create(array('amount' => 50));
-        $refB = $charge->refunds->create(array('amount' => 50));
-
-        $all = $charge->refunds->all();
-        $this->assertSame(false, $all['has_more']);
-        $this->assertSame(2, count($all->data));
-        $this->assertSame($refB->id, $all->data[0]->id);
-        $this->assertSame($refA->id, $all->data[1]->id);
-    }
-}
diff --git a/tests/SourceTest.php b/tests/SourceTest.php
deleted file mode 100644
index eeb729b93..000000000
--- a/tests/SourceTest.php
+++ /dev/null
@@ -1,250 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class SourceTest extends TestCase
-{
-    public function testRetrieve()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/sources/src_foo',
-            array(),
-            array(
-                'id' => 'src_foo',
-                'object' => 'source',
-            )
-        );
-        $source = Source::retrieve('src_foo');
-        $this->assertSame($source->id, 'src_foo');
-    }
-
-    public function testCreate()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/sources',
-            array(
-                'type' => 'bitcoin',
-                'amount' => 1000,
-                'currency' => 'usd',
-                'owner' => array('email' => 'jenny.rosen@example.com'),
-            ),
-            array(
-                'id' => 'src_foo',
-                'object' => 'source'
-            )
-        );
-        $source = Source::create(array(
-            'type' => 'bitcoin',
-            'amount' => 1000,
-            'currency' => 'usd',
-            'owner' => array('email' => 'jenny.rosen@example.com'),
-        ));
-        $this->assertSame($source->id, 'src_foo');
-    }
-
-    public function testSave()
-    {
-        $response = array(
-            'id' => 'src_foo',
-            'object' => 'source',
-            'metadata' => array(),
-        );
-        $this->mockRequest(
-            'GET',
-            '/v1/sources/src_foo',
-            array(),
-            $response
-        );
-
-        $response['metadata'] = array('foo' => 'bar');
-        $this->mockRequest(
-            'POST',
-            '/v1/sources/src_foo',
-            array(
-                'metadata' => array('foo' => 'bar'),
-            ),
-            $response
-        );
-
-        $source = Source::retrieve('src_foo');
-        $source->metadata['foo'] = 'bar';
-        $source->save();
-        $this->assertSame($source->metadata['foo'], 'bar');
-    }
-
-    public function testSaveOwner()
-    {
-        $response = array(
-            'id' => 'src_foo',
-            'object' => 'source',
-            'owner' => array(
-                'name' => null,
-                'address' => null,
-            ),
-        );
-        $this->mockRequest(
-            'GET',
-            '/v1/sources/src_foo',
-            array(),
-            $response
-        );
-
-        $response['owner'] = array(
-            'name' => "Stripey McStripe",
-            'address' => array(
-                'line1' => "Test Address",
-                'city' => "Test City",
-                'postal_code' => "12345",
-                'state' => "Test State",
-                'country' => "Test Country",
-            )
-        );
-        $this->mockRequest(
-            'POST',
-            '/v1/sources/src_foo',
-            array(
-                'owner' => array(
-                    'name' => "Stripey McStripe",
-                    'address' => array(
-                        'line1' => "Test Address",
-                        'city' => "Test City",
-                        'postal_code' => "12345",
-                        'state' => "Test State",
-                        'country' => "Test Country",
-                    ),
-                ),
-            ),
-            $response
-        );
-
-        $source = Source::retrieve('src_foo');
-        $source->owner['name'] = "Stripey McStripe";
-        $source->owner['address'] = array(
-            'line1' => "Test Address",
-            'city' => "Test City",
-            'postal_code' => "12345",
-            'state' => "Test State",
-            'country' => "Test Country",
-        );
-        $source->save();
-        $this->assertSame($source->owner['name'], "Stripey McStripe");
-        $this->assertSame($source->owner['address']['line1'], "Test Address");
-        $this->assertSame($source->owner['address']['city'], "Test City");
-        $this->assertSame($source->owner['address']['postal_code'], "12345");
-        $this->assertSame($source->owner['address']['state'], "Test State");
-        $this->assertSame($source->owner['address']['country'], "Test Country");
-    }
-
-
-    public function testSaveCardExpiryDate()
-    {
-        $response = array(
-            'id' => 'src_foo',
-            'object' => 'source',
-            'card' => array(
-                'exp_month' => 8,
-                'exp_year' => 2019,
-            ),
-        );
-        $source = Source::constructFrom(
-            $response,
-            new Util\RequestOptions()
-        );
-
-        $response['card']['exp_month'] = 12;
-        $response['card']['exp_year'] = 2022;
-        $this->mockRequest(
-            'POST',
-            '/v1/sources/src_foo',
-            array(
-                'card' => array(
-                    'exp_month' => 12,
-                    'exp_year' => 2022,
-                )
-            ),
-            $response
-        );
-
-        $source->card->exp_month = 12;
-        $source->card->exp_year = 2022;
-        $source->save();
-
-        $this->assertSame(12, $source->card->exp_month);
-        $this->assertSame(2022, $source->card->exp_year);
-    }
-
-    public function testDetachAttached()
-    {
-        $response = array(
-            'id' => 'src_foo',
-            'object' => 'source',
-            'customer' => 'cus_bar',
-        );
-        $source = Source::constructFrom(
-            $response,
-            new Util\RequestOptions()
-        );
-
-        unset($response['customer']);
-        $this->mockRequest(
-            'DELETE',
-            '/v1/customers/cus_bar/sources/src_foo',
-            array(),
-            $response
-        );
-
-        $source->detach();
-        $this->assertFalse(array_key_exists('customer', $source));
-    }
-
-    /**
-     * @expectedException Stripe\Error\Api
-     */
-    public function testDetachUnattached()
-    {
-        $response = array(
-            'id' => 'src_foo',
-            'object' => 'source',
-        );
-        $source = Source::constructFrom(
-            $response,
-            new Util\RequestOptions()
-        );
-
-        $source->detach();
-    }
-
-    public function testVerify()
-    {
-        $response = array(
-            'id' => 'src_foo',
-            'object' => 'source',
-            'verification' => array('status' => 'pending'),
-        );
-        $this->mockRequest(
-            'GET',
-            '/v1/sources/src_foo',
-            array(),
-            $response
-        );
-
-        $response['verification']['status'] = 'succeeded';
-        $this->mockRequest(
-            'POST',
-            '/v1/sources/src_foo/verify',
-            array(
-                'values' => array(32, 45),
-            ),
-            $response
-        );
-
-        $source = Source::retrieve('src_foo');
-        $this->assertSame($source->verification->status, 'pending');
-        $source->verify(array(
-            'values' => array(32, 45),
-        ));
-        $this->assertSame($source->verification->status, 'succeeded');
-    }
-}
diff --git a/tests/SourceTransactionTest.php b/tests/SourceTransactionTest.php
deleted file mode 100644
index 6bbff94cb..000000000
--- a/tests/SourceTransactionTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class SourceTransactionTest extends TestCase
-{
-    public function testList()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/sources/src_foo/source_transactions',
-            array(),
-            array(
-                'object' => 'list',
-                'url' => '/v1/sources/src_foo/source_transactions',
-                'data' => array(
-                    array(
-                        'id' => 'srctxn_bar',
-                        'object' => 'source_transaction',
-                    ),
-                ),
-                'has_more' => false,
-            )
-        );
-
-        $source = \Stripe\Source::constructFrom(
-            array('id' => 'src_foo', 'object' => 'source'),
-            new \Stripe\Util\RequestOptions()
-        );
-
-        $transactions = $source->sourceTransactions();
-
-        $this->assertTrue(is_array($transactions->data));
-        $this->assertSame('source_transaction', $transactions->data[0]->object);
-    }
-}
diff --git a/tests/Stripe/AccountTest.php b/tests/Stripe/AccountTest.php
new file mode 100644
index 000000000..a49c00dd9
--- /dev/null
+++ b/tests/Stripe/AccountTest.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Stripe;
+
+class AccountTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'acct_123';
+    const TEST_EXTERNALACCOUNT_ID = 'ba_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/accounts'
+        );
+        $resources = Account::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Account", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Account::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Account", get_class($resource));
+    }
+
+    public function testIsRetrievableWithoutId()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/account'
+        );
+        $resource = Account::retrieve();
+        $this->assertSame("Stripe\\Account", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts'
+        );
+        $resource = Account::create(array("type" => "custom"));
+        $this->assertSame("Stripe\\Account", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Account::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Account", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Account::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Account", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = Account::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Account", get_class($resource));
+    }
+
+    public function testIsRejectable()
+    {
+        $account = Account::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts/' . $account->id . '/reject'
+        );
+        $resource = $account->reject(array("reason" => "fraud"));
+        $this->assertSame("Stripe\\Account", get_class($resource));
+        $this->assertSame($resource, $account);
+    }
+
+    public function testIsDeauthorizable()
+    {
+        $resource = Account::retrieve(self::TEST_RESOURCE_ID);
+        $this->stubRequest(
+            'post',
+            '/oauth/deauthorize',
+            array(
+                'client_id' => Stripe::getClientId(),
+                'stripe_user_id' => $resource->id,
+            ),
+            null,
+            false,
+            array(
+                'stripe_user_id' => $resource->id,
+            ),
+            200,
+            Stripe::$connectBase
+        );
+        $resource->deauthorize();
+    }
+
+    public function testCanCreateExternalAccount()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID . '/external_accounts'
+        );
+        $resource = Account::createExternalAccount(self::TEST_RESOURCE_ID, array("external_account" => "btok_123"));
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanRetrieveExternalAccount()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID . '/external_accounts/' . self::TEST_EXTERNALACCOUNT_ID
+        );
+        $resource = Account::retrieveExternalAccount(self::TEST_RESOURCE_ID, self::TEST_EXTERNALACCOUNT_ID);
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanUpdateExternalAccount()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID . '/external_accounts/' . self::TEST_EXTERNALACCOUNT_ID
+        );
+        $resource = Account::updateExternalAccount(self::TEST_RESOURCE_ID, self::TEST_EXTERNALACCOUNT_ID, array("name" => "name"));
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanDeleteExternalAccount()
+    {
+        $this->expectsRequest(
+            'delete',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID . '/external_accounts/' . self::TEST_EXTERNALACCOUNT_ID
+        );
+        $resource = Account::deleteExternalAccount(self::TEST_RESOURCE_ID, self::TEST_EXTERNALACCOUNT_ID);
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanListExternalAccounts()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID . '/external_accounts'
+        );
+        $resources = Account::allExternalAccounts(self::TEST_RESOURCE_ID);
+        $this->assertTrue(is_array($resources->data));
+    }
+
+    public function testCanCreateLoginLink()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/accounts/' . self::TEST_RESOURCE_ID . '/login_links'
+        );
+        $resource = Account::createLoginLink(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\LoginLink", get_class($resource));
+    }
+}
diff --git a/tests/ApiRequestorTest.php b/tests/Stripe/ApiRequestorTest.php
similarity index 62%
rename from tests/ApiRequestorTest.php
rename to tests/Stripe/ApiRequestorTest.php
index 465f0c19f..bfbff6aff 100644
--- a/tests/ApiRequestorTest.php
+++ b/tests/Stripe/ApiRequestorTest.php
@@ -69,12 +69,14 @@ public function testDefaultHeaders()
         $this->assertSame($headers['Authorization'], 'Bearer ' . $apiKey);
     }
 
-    public function testErrorInvalidRequest()
+    public function testRaisesInvalidRequestErrorOn400()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/v1/charges',
             array(),
+            null,
+            false,
             array(
                 'error' => array(
                     'type' => 'invalid_request_error',
@@ -89,6 +91,8 @@ public function testErrorInvalidRequest()
             Charge::create();
             $this->fail("Did not raise error");
         } catch (Error\InvalidRequest $e) {
+            $this->assertSame(400, $e->getHttpStatus());
+            $this->assertTrue(is_array($e->getJsonBody()));
             $this->assertSame('Missing id', $e->getMessage());
             $this->assertSame('id', $e->getStripeParam());
         } catch (\Exception $e) {
@@ -96,12 +100,14 @@ public function testErrorInvalidRequest()
         }
     }
 
-    public function testErrorAuthentication()
+    public function testRaisesAuthenticationErrorOn401()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/v1/charges',
             array(),
+            null,
+            false,
             array(
                 'error' => array(
                     'type' => 'invalid_request_error',
@@ -115,18 +121,22 @@ public function testErrorAuthentication()
             Charge::create();
             $this->fail("Did not raise error");
         } catch (Error\Authentication $e) {
+            $this->assertSame(401, $e->getHttpStatus());
+            $this->assertTrue(is_array($e->getJsonBody()));
             $this->assertSame('You did not provide an API key.', $e->getMessage());
         } catch (\Exception $e) {
             $this->fail("Unexpected exception: " . get_class($e));
         }
     }
 
-    public function testErrorCard()
+    public function testRaisesCardErrorOn402()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/v1/charges',
             array(),
+            null,
+            false,
             array(
                 'error' => array(
                     'type' => 'card_error',
@@ -143,6 +153,8 @@ public function testErrorCard()
             Charge::create();
             $this->fail("Did not raise error");
         } catch (Error\Card $e) {
+            $this->assertSame(402, $e->getHttpStatus());
+            $this->assertTrue(is_array($e->getJsonBody()));
             $this->assertSame('Your card was declined.', $e->getMessage());
             $this->assertSame('card_declined', $e->getStripeCode());
             $this->assertSame('generic_decline', $e->getDeclineCode());
@@ -151,12 +163,102 @@ public function testErrorCard()
         }
     }
 
-    public function testErrorOAuthInvalidRequest()
+    public function testRaisesPermissionErrorOn403()
     {
-        $this->mockRequest(
+        $this->stubRequest(
+            'GET',
+            '/v1/accounts/foo',
+            array(),
+            null,
+            false,
+            array(
+                'error' => array(
+                    'type' => 'invalid_request_error',
+                    'message' => "The provided key 'sk_test_********************1234' does not have access to account 'foo' (or that account does not exist). Application access may have been revoked.",
+                ),
+            ),
+            403
+        );
+
+        try {
+            Account::retrieve('foo');
+            $this->fail("Did not raise error");
+        } catch (Error\Permission $e) {
+            $this->assertSame(403, $e->getHttpStatus());
+            $this->assertTrue(is_array($e->getJsonBody()));
+            $this->assertSame("The provided key 'sk_test_********************1234' does not have access to account 'foo' (or that account does not exist). Application access may have been revoked.", $e->getMessage());
+        } catch (\Exception $e) {
+            $this->fail("Unexpected exception: " . get_class($e));
+        }
+    }
+
+    public function testRaisesInvalidRequestErrorOn404()
+    {
+        $this->stubRequest(
+            'GET',
+            '/v1/charges/foo',
+            array(),
+            null,
+            false,
+            array(
+                'error' => array(
+                    'type' => 'invalid_request_error',
+                    'message' => 'No such charge: foo',
+                    'param' => 'id',
+                ),
+            ),
+            404
+        );
+
+        try {
+            Charge::retrieve('foo');
+            $this->fail("Did not raise error");
+        } catch (Error\InvalidRequest $e) {
+            $this->assertSame(404, $e->getHttpStatus());
+            $this->assertTrue(is_array($e->getJsonBody()));
+            $this->assertSame('No such charge: foo', $e->getMessage());
+            $this->assertSame('id', $e->getStripeParam());
+        } catch (\Exception $e) {
+            $this->fail("Unexpected exception: " . get_class($e));
+        }
+    }
+
+    public function testRaisesRateLimitErrorOn429()
+    {
+        $this->stubRequest(
+            'POST',
+            '/v1/charges',
+            array(),
+            null,
+            false,
+            array(
+                'error' => array(
+                    'message' => 'Too many requests',
+                ),
+            ),
+            429
+        );
+
+        try {
+            Charge::create();
+            $this->fail("Did not raise error");
+        } catch (Error\RateLimit $e) {
+            $this->assertSame(429, $e->getHttpStatus());
+            $this->assertTrue(is_array($e->getJsonBody()));
+            $this->assertSame('Too many requests', $e->getMessage());
+        } catch (\Exception $e) {
+            $this->fail("Unexpected exception: " . get_class($e));
+        }
+    }
+
+    public function testRaisesOAuthInvalidRequestError()
+    {
+        $this->stubRequest(
             'POST',
             '/oauth/token',
             array(),
+            null,
+            false,
             array(
                 'error' => 'invalid_request',
                 'error_description' => 'No grant type specified',
@@ -169,6 +271,7 @@ public function testErrorOAuthInvalidRequest()
             OAuth::token();
             $this->fail("Did not raise error");
         } catch (Error\OAuth\InvalidRequest $e) {
+            $this->assertSame(400, $e->getHttpStatus());
             $this->assertSame('invalid_request', $e->getErrorCode());
             $this->assertSame('No grant type specified', $e->getMessage());
         } catch (\Exception $e) {
@@ -176,12 +279,14 @@ public function testErrorOAuthInvalidRequest()
         }
     }
 
-    public function testErrorOAuthInvalidClient()
+    public function testRaisesOAuthInvalidClientError()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/oauth/token',
             array(),
+            null,
+            false,
             array(
                 'error' => 'invalid_client',
                 'error_description' => 'No authentication was provided. Send your secret API key using the Authorization header, or as a client_secret POST parameter.',
@@ -194,6 +299,7 @@ public function testErrorOAuthInvalidClient()
             OAuth::token();
             $this->fail("Did not raise error");
         } catch (Error\OAuth\InvalidClient $e) {
+            $this->assertSame(401, $e->getHttpStatus());
             $this->assertSame('invalid_client', $e->getErrorCode());
             $this->assertSame('No authentication was provided. Send your secret API key using the Authorization header, or as a client_secret POST parameter.', $e->getMessage());
         } catch (\Exception $e) {
@@ -201,12 +307,14 @@ public function testErrorOAuthInvalidClient()
         }
     }
 
-    public function testErrorOAuthInvalidGrant()
+    public function testRaisesOAuthInvalidGrantError()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/oauth/token',
             array(),
+            null,
+            false,
             array(
                 'error' => 'invalid_grant',
                 'error_description' => 'This authorization code has already been used. All tokens issued with this code have been revoked.',
@@ -219,6 +327,7 @@ public function testErrorOAuthInvalidGrant()
             OAuth::token();
             $this->fail("Did not raise error");
         } catch (Error\OAuth\InvalidGrant $e) {
+            $this->assertSame(400, $e->getHttpStatus());
             $this->assertSame('invalid_grant', $e->getErrorCode());
             $this->assertSame('This authorization code has already been used. All tokens issued with this code have been revoked.', $e->getMessage());
         } catch (\Exception $e) {
diff --git a/tests/Stripe/ApplePayDomainTest.php b/tests/Stripe/ApplePayDomainTest.php
new file mode 100644
index 000000000..e80c909d1
--- /dev/null
+++ b/tests/Stripe/ApplePayDomainTest.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Stripe;
+
+class ApplePayDomainTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'apwc_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/apple_pay/domains'
+        );
+        $resources = ApplePayDomain::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\ApplePayDomain", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/apple_pay/domains/' . self::TEST_RESOURCE_ID
+        );
+        $resource = ApplePayDomain::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\ApplePayDomain", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/apple_pay/domains'
+        );
+        $resource = ApplePayDomain::create(array(
+            "domain_name" => "domain",
+        ));
+        $this->assertSame("Stripe\\ApplePayDomain", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = ApplePayDomain::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/apple_pay/domains/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\ApplePayDomain", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/ApplicationFeeRefundTest.php b/tests/Stripe/ApplicationFeeRefundTest.php
new file mode 100644
index 000000000..9ca737b4a
--- /dev/null
+++ b/tests/Stripe/ApplicationFeeRefundTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Stripe;
+
+class ApplicationFeeRefundTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'fr_123';
+    const TEST_FEE_ID = 'fee_123';
+
+    public function testIsSaveable()
+    {
+        $resource = ApplicationFee::retrieveRefund(self::TEST_FEE_ID, self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/application_fees/' . $resource->fee . '/refunds/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\ApplicationFeeRefund", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/ApplicationFeeTest.php b/tests/Stripe/ApplicationFeeTest.php
new file mode 100644
index 000000000..cddd8d74c
--- /dev/null
+++ b/tests/Stripe/ApplicationFeeTest.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Stripe;
+
+class ApplicationFeeTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'fee_123';
+    const TEST_FEEREFUND_ID = 'fr_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/application_fees'
+        );
+        $resources = ApplicationFee::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\ApplicationFee", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/application_fees/' . self::TEST_RESOURCE_ID
+        );
+        $resource = ApplicationFee::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\ApplicationFee", get_class($resource));
+    }
+
+    public function testCanCreateRefund()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/application_fees/' . self::TEST_RESOURCE_ID . '/refunds'
+        );
+        $resource = ApplicationFee::createRefund(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\ApplicationFeeRefund", get_class($resource));
+    }
+
+    public function testCanRetrieveRefund()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/application_fees/' . self::TEST_RESOURCE_ID . '/refunds/' . self::TEST_FEEREFUND_ID
+        );
+        $resource = ApplicationFee::retrieveRefund(self::TEST_RESOURCE_ID, self::TEST_FEEREFUND_ID);
+        $this->assertSame("Stripe\\ApplicationFeeRefund", get_class($resource));
+    }
+
+    public function testCanUpdateRefund()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/application_fees/' . self::TEST_RESOURCE_ID . '/refunds/' . self::TEST_FEEREFUND_ID
+        );
+        $resource = ApplicationFee::updateRefund(self::TEST_RESOURCE_ID, self::TEST_FEEREFUND_ID);
+        $this->assertSame("Stripe\\ApplicationFeeRefund", get_class($resource));
+    }
+
+    public function testCanListRefunds()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/application_fees/' . self::TEST_RESOURCE_ID . '/refunds'
+        );
+        $resources = ApplicationFee::allRefunds(self::TEST_RESOURCE_ID);
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\ApplicationFeeRefund", get_class($resources->data[0]));
+    }
+}
diff --git a/tests/AttachedObjectTest.php b/tests/Stripe/AttachedObjectTest.php
similarity index 100%
rename from tests/AttachedObjectTest.php
rename to tests/Stripe/AttachedObjectTest.php
diff --git a/tests/Stripe/BalanceTest.php b/tests/Stripe/BalanceTest.php
new file mode 100644
index 000000000..4ba634a8b
--- /dev/null
+++ b/tests/Stripe/BalanceTest.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Stripe;
+
+class BalanceTest extends TestCase
+{
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/balance'
+        );
+        $resource = Balance::retrieve();
+        $this->assertSame("Stripe\\Balance", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/BalanceTransactionTest.php b/tests/Stripe/BalanceTransactionTest.php
new file mode 100644
index 000000000..76f571f4f
--- /dev/null
+++ b/tests/Stripe/BalanceTransactionTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Stripe;
+
+class BalanceTransactionTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'txn_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/balance/history'
+        );
+        $resources = BalanceTransaction::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\BalanceTransaction", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/balance/history/' . self::TEST_RESOURCE_ID
+        );
+        $resource = BalanceTransaction::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\BalanceTransaction", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/BankAccountTest.php b/tests/Stripe/BankAccountTest.php
new file mode 100644
index 000000000..74fa71fe4
--- /dev/null
+++ b/tests/Stripe/BankAccountTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Stripe;
+
+class BankAccountTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'ba_123';
+
+    public function testIsVerifiable()
+    {
+        $resource = BankAccount::constructFrom(
+            array(
+                'id' => self::TEST_RESOURCE_ID,
+                'object' => 'bank_account',
+                'customer' => 'cus_123',
+            ),
+            new Util\RequestOptions()
+        );
+        $this->expectsRequest(
+            'post',
+            '/v1/customers/cus_123/sources/' . self::TEST_RESOURCE_ID . "/verify",
+            array(
+                "amounts" => array(1, 2)
+            )
+        );
+        $resource->verify(array("amounts" => array(1, 2)));
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+}
diff --git a/tests/BitcoinReceiverTest.php b/tests/Stripe/BitcoinReceiverTest.php
similarity index 100%
rename from tests/BitcoinReceiverTest.php
rename to tests/Stripe/BitcoinReceiverTest.php
diff --git a/tests/Stripe/ChargeTest.php b/tests/Stripe/ChargeTest.php
new file mode 100644
index 000000000..e047c0653
--- /dev/null
+++ b/tests/Stripe/ChargeTest.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Stripe;
+
+class ChargeTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'ch_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/charges'
+        );
+        $resources = Charge::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Charge", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/charges/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/charges'
+        );
+        $resource = Charge::create(array(
+            "amount" => 100,
+            "currency" => "usd",
+            "source" => "tok_123"
+        ));
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Charge::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+    }
+
+    public function testCanRefund()
+    {
+        $charge = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $charge->id . '/refund'
+        );
+        $resource = $charge->refund();
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+        $this->assertSame($resource, $charge);
+    }
+
+    public function testCanCapture()
+    {
+        $charge = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $charge->id . '/capture'
+        );
+        $resource = $charge->capture();
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+        $this->assertSame($resource, $charge);
+    }
+
+    public function testCanUpdateDispute()
+    {
+        $charge = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $charge->id . '/dispute'
+        );
+        $resource = $charge->updateDispute();
+        $this->assertSame("Stripe\\Dispute", get_class($resource));
+    }
+
+    public function testCanCloseDispute()
+    {
+        $charge = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $charge->id . '/dispute/close'
+        );
+        $resource = $charge->closeDispute();
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+        $this->assertSame($resource, $charge);
+    }
+
+    public function testCanMarkAsFraudulent()
+    {
+        $charge = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $charge->id,
+            array('fraud_details' => array('user_report' => 'fraudulent'))
+        );
+        $resource = $charge->markAsFraudulent();
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+        $this->assertSame($resource, $charge);
+    }
+
+    public function testCanMarkAsSafe()
+    {
+        $charge = Charge::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/charges/' . $charge->id,
+            array('fraud_details' => array('user_report' => 'safe'))
+        );
+        $resource = $charge->markAsSafe();
+        $this->assertSame("Stripe\\Charge", get_class($resource));
+        $this->assertSame($resource, $charge);
+    }
+}
diff --git a/tests/Stripe/CollectionTest.php b/tests/Stripe/CollectionTest.php
new file mode 100644
index 000000000..e012ddc55
--- /dev/null
+++ b/tests/Stripe/CollectionTest.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Stripe;
+
+class CollectionTest extends TestCase
+{
+    /**
+     * @before
+     */
+    public function setUpFixture()
+    {
+        $this->fixture = Collection::constructFrom(array(
+            'data' => array(array('id' => 1)),
+            'has_more' => true,
+            'url' => '/things',
+        ), new Util\RequestOptions());
+    }
+
+    public function testCanList()
+    {
+        $this->stubRequest(
+            'GET',
+            '/things',
+            array(),
+            null,
+            false,
+            array(
+                'data' => array(array('id' => 1)),
+                'has_more' => true,
+                'url' => '/things',
+            )
+        );
+
+        $resources = $this->fixture->all();
+        $this->assertTrue(is_array($resources->data));
+    }
+
+    public function testCanRetrieve()
+    {
+        $this->stubRequest(
+            'GET',
+            '/things/1',
+            array(),
+            null,
+            false,
+            array(
+                'id' => 1,
+            )
+        );
+
+        $this->fixture->retrieve(1);
+    }
+
+    public function testCanCreate()
+    {
+        $this->stubRequest(
+            'POST',
+            '/things',
+            array(
+                'foo' => 'bar',
+            ),
+            null,
+            false,
+            array(
+                'id' => 2,
+            )
+        );
+
+        $this->fixture->create(array(
+            'foo' => 'bar',
+        ));
+    }
+
+    public function testProvidesAutoPagingIterator()
+    {
+        $this->stubRequest(
+            'GET',
+            '/things',
+            array(
+                'starting_after' => 1,
+            ),
+            null,
+            false,
+            array(
+                'data' => array(array('id' => 2), array('id' => 3)),
+                'has_more' => false,
+            )
+        );
+
+        $seen = array();
+        foreach ($this->fixture->autoPagingIterator() as $item) {
+            array_push($seen, $item['id']);
+        }
+
+        $this->assertSame(array(1, 2, 3), $seen);
+    }
+
+    public function testSupportsIteratorToArray()
+    {
+        $this->stubRequest(
+            'GET',
+            '/things',
+            array(
+                'starting_after' => 1,
+            ),
+            null,
+            false,
+            array(
+                'data' => array(array('id' => 2), array('id' => 3)),
+                'has_more' => false,
+            )
+        );
+
+        $seen = array();
+        foreach (iterator_to_array($this->fixture->autoPagingIterator()) as $item) {
+            array_push($seen, $item['id']);
+        }
+
+        $this->assertSame(array(1, 2, 3), $seen);
+    }
+}
diff --git a/tests/Stripe/CountrySpecTest.php b/tests/Stripe/CountrySpecTest.php
new file mode 100644
index 000000000..af6635a0a
--- /dev/null
+++ b/tests/Stripe/CountrySpecTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Stripe;
+
+class CountrySpecTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'US';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/country_specs'
+        );
+        $resources = CountrySpec::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\CountrySpec", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/country_specs/' . self::TEST_RESOURCE_ID
+        );
+        $resource = CountrySpec::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\CountrySpec", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/CouponTest.php b/tests/Stripe/CouponTest.php
new file mode 100644
index 000000000..cf239c637
--- /dev/null
+++ b/tests/Stripe/CouponTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Stripe;
+
+class CouponTest extends TestCase
+{
+    const TEST_RESOURCE_ID = '25OFF';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/coupons'
+        );
+        $resources = Coupon::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Coupon", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/coupons/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Coupon::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Coupon", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/coupons'
+        );
+        $resource = Coupon::create(array(
+            "percent_off" => 25,
+            "duration" => "repeating",
+            "duration_in_months" => 3,
+            "id" => self::TEST_RESOURCE_ID,
+        ));
+        $this->assertSame("Stripe\\Coupon", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Coupon::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/coupons/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Coupon", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/coupons/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Coupon::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Coupon", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = Coupon::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/coupons/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Coupon", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/CustomerTest.php b/tests/Stripe/CustomerTest.php
new file mode 100644
index 000000000..0dff775eb
--- /dev/null
+++ b/tests/Stripe/CustomerTest.php
@@ -0,0 +1,233 @@
+<?php
+
+namespace Stripe;
+
+class CustomerTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'cus_123';
+    const TEST_SOURCE_ID = 'ba_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/customers'
+        );
+        $resources = Customer::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Customer", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/customers/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Customer", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/customers'
+        );
+        $resource = Customer::create();
+        $this->assertSame("Stripe\\Customer", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/customers/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Customer", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/customers/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Customer::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Customer", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/customers/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Customer", get_class($resource));
+    }
+
+    public function testCanAddInvoiceItem()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/invoiceitems',
+            array(
+                "amount" => 100,
+                "currency" => "usd",
+                "customer" => $customer->id
+            )
+        );
+        $resource = $customer->addInvoiceItem(array(
+            "amount" => 100,
+            "currency" => "usd"
+        ));
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resource));
+    }
+
+    public function testCanListInvoices()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'get',
+            '/v1/invoices',
+            array("customer" => $customer->id)
+        );
+        $resources = $customer->invoices();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Invoice", get_class($resources->data[0]));
+    }
+
+    public function testCanListInvoiceItems()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'get',
+            '/v1/invoiceitems',
+            array("customer" => $customer->id)
+        );
+        $resources = $customer->invoiceItems();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resources->data[0]));
+    }
+
+    public function testCanListCharges()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'get',
+            '/v1/charges',
+            array("customer" => $customer->id)
+        );
+        $resources = $customer->charges();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Charge", get_class($resources->data[0]));
+    }
+
+    public function testCanUpdateSubscription()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->stubRequest(
+            'post',
+            '/v1/customers/' . $customer->id . '/subscription',
+            array("plan" => "plan"),
+            null,
+            false,
+            array(
+                "object" => "subscription",
+                "id" => "sub_foo"
+            )
+        );
+        $resource = $customer->updateSubscription(array("plan" => "plan"));
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+        $this->assertSame("sub_foo", $customer->subscription->id);
+    }
+
+    public function testCanCancelSubscription()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->stubRequest(
+            'delete',
+            '/v1/customers/' . $customer->id . '/subscription',
+            array(),
+            null,
+            false,
+            array(
+                "object" => "subscription",
+                "id" => "sub_foo"
+            )
+        );
+        $resource = $customer->cancelSubscription();
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+        $this->assertSame("sub_foo", $customer->subscription->id);
+    }
+
+    public function testCanDeleteDiscount()
+    {
+        $customer = Customer::retrieve(self::TEST_RESOURCE_ID);
+        $this->stubRequest(
+            'delete',
+            '/v1/customers/' . $customer->id . '/discount'
+        );
+        $customer->deleteDiscount();
+        $this->assertSame($customer->discount, null);
+    }
+
+    public function testCanCreateSource()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/customers/' . self::TEST_RESOURCE_ID . '/sources'
+        );
+        $resource = Customer::createSource(self::TEST_RESOURCE_ID, array("source" => "btok_123"));
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanRetrieveSource()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/customers/' . self::TEST_RESOURCE_ID . '/sources/' . self::TEST_SOURCE_ID
+        );
+        $resource = Customer::retrieveSource(self::TEST_RESOURCE_ID, self::TEST_SOURCE_ID);
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanUpdateSource()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/customers/' . self::TEST_RESOURCE_ID . '/sources/' . self::TEST_SOURCE_ID
+        );
+        $resource = Customer::updateSource(self::TEST_RESOURCE_ID, self::TEST_SOURCE_ID, array("name" => "name"));
+        // stripe-mock returns a Card on this method and not a bank account
+        $this->assertSame("Stripe\\Card", get_class($resource));
+    }
+
+    public function testCanDeleteSource()
+    {
+        $this->expectsRequest(
+            'delete',
+            '/v1/customers/' . self::TEST_RESOURCE_ID . '/sources/' . self::TEST_SOURCE_ID
+        );
+        $resource = Customer::deleteSource(self::TEST_RESOURCE_ID, self::TEST_SOURCE_ID);
+        $this->assertSame("Stripe\\BankAccount", get_class($resource));
+    }
+
+    public function testCanListSources()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/customers/' . self::TEST_RESOURCE_ID . '/sources'
+        );
+        $resources = Customer::allSources(self::TEST_RESOURCE_ID);
+        $this->assertTrue(is_array($resources->data));
+    }
+}
diff --git a/tests/Stripe/DisputeTest.php b/tests/Stripe/DisputeTest.php
new file mode 100644
index 000000000..c7442d4ab
--- /dev/null
+++ b/tests/Stripe/DisputeTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Stripe;
+
+class DisputeTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'dp_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/disputes'
+        );
+        $resources = Dispute::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Dispute", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/disputes/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Dispute::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Dispute", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Dispute::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/disputes/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Dispute", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/disputes/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Dispute::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Dispute", get_class($resource));
+    }
+
+    public function testIsClosable()
+    {
+        $dispute = Dispute::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/disputes/' . $dispute->id . '/close'
+        );
+        $resource = $dispute->close();
+        $this->assertSame("Stripe\\Dispute", get_class($resource));
+        $this->assertSame($resource, $dispute);
+    }
+}
diff --git a/tests/Stripe/EphemeralKeyTest.php b/tests/Stripe/EphemeralKeyTest.php
new file mode 100644
index 000000000..a1ddd0e3e
--- /dev/null
+++ b/tests/Stripe/EphemeralKeyTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Stripe;
+
+class EphemeralKeyTest extends TestCase
+{
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/ephemeral_keys',
+            null,
+            array("Stripe-Version: 2017-05-25")
+        );
+        $resource = EphemeralKey::create(array(
+            "customer" => "cus_123",
+        ), array("stripe_version" => "2017-05-25"));
+        $this->assertSame("Stripe\\EphemeralKey", get_class($resource));
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testIsNotCreatableWithoutAnExplicitApiVersion()
+    {
+        $resource = EphemeralKey::create(array(
+            "customer" => "cus_123",
+        ));
+    }
+
+    public function testIsDeletable()
+    {
+        $key = EphemeralKey::create(array(
+            "customer" => "cus_123",
+        ), array("stripe_version" => "2017-05-25"));
+        $this->expectsRequest(
+            'delete',
+            '/v1/ephemeral_keys/' . $key->id
+        );
+        $resource = $key->delete();
+        $this->assertSame("Stripe\\EphemeralKey", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/EventTest.php b/tests/Stripe/EventTest.php
new file mode 100644
index 000000000..9dbda24d8
--- /dev/null
+++ b/tests/Stripe/EventTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Stripe;
+
+class EventTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'evt_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/events'
+        );
+        $resources = Event::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Event", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/events/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Event::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Event", get_class($resource));
+    }
+}
diff --git a/tests/ExchangeRateTest.php b/tests/Stripe/ExchangeRateTest.php
similarity index 79%
rename from tests/ExchangeRateTest.php
rename to tests/Stripe/ExchangeRateTest.php
index 507a60e24..ccf92efd4 100644
--- a/tests/ExchangeRateTest.php
+++ b/tests/Stripe/ExchangeRateTest.php
@@ -4,30 +4,14 @@
 
 class ExchangeRateTest extends TestCase
 {
-    public function testRetrieve()
+    public function testIsListable()
     {
-        $this->mockRequest(
-            'GET',
-            '/v1/exchange_rates/usd',
-            array(),
-            array(
-                'id' => 'usd',
-                'object' => 'exchange_rate',
-                'rates' => array('eur' => 0.845876),
-            )
-        );
-
-        $currency = "usd";
-        $rates = ExchangeRate::retrieve($currency);
-        $this->assertEquals('exchange_rate', $rates->object);
-    }
-
-    public function testList()
-    {
-        $this->mockRequest(
-            'GET',
+        $this->stubRequest(
+            'get',
             '/v1/exchange_rates',
             array(),
+            null,
+            false,
             array(
                 'object' => 'list',
                 'data' => array(
@@ -49,4 +33,22 @@ public function testList()
         $this->assertTrue(is_array($listRates->data));
         $this->assertEquals('exchange_rate', $listRates->data[0]->object);
     }
+
+    public function testIsRetrievable()
+    {
+        $this->stubRequest(
+            'get',
+            '/v1/exchange_rates/usd',
+            array(),
+            null,
+            false,
+            array(
+                'id' => 'usd',
+                'object' => 'exchange_rate',
+                'rates' => array('eur' => 0.845876),
+            )
+        );
+        $rates = ExchangeRate::retrieve("usd");
+        $this->assertEquals('exchange_rate', $rates->object);
+    }
 }
diff --git a/tests/Stripe/FileUploadTest.php b/tests/Stripe/FileUploadTest.php
new file mode 100644
index 000000000..794777d9b
--- /dev/null
+++ b/tests/Stripe/FileUploadTest.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Stripe;
+
+class FileUploadTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'file_123';
+
+    /**
+     * @before
+     */
+    public function setUpFixture()
+    {
+        // PHP <= 5.5 does not support arrays as class constants, so we set up
+        // the fixture as an instance variable.
+        $this->fixture = array(
+            'id' => self::TEST_RESOURCE_ID,
+            'object' => 'file_upload',
+        );
+    }
+
+    public function testIsListable()
+    {
+        $this->stubRequest(
+            'get',
+            '/v1/files',
+            array(),
+            null,
+            false,
+            array(
+                'object' => 'list',
+                'data' => array($this->fixture),
+                'resource_url' => '/v1/files',
+            ),
+            200,
+            Stripe::$apiUploadBase
+        );
+
+        $resources = FileUpload::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\FileUpload", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->stubRequest(
+            'get',
+            '/v1/files/' . self::TEST_RESOURCE_ID,
+            array(),
+            null,
+            false,
+            $this->fixture,
+            200,
+            Stripe::$apiUploadBase
+        );
+        $resource = FileUpload::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\FileUpload", get_class($resource));
+    }
+
+    public function testIsCreatableWithFileHandle()
+    {
+        $this->stubRequest(
+            'post',
+            '/v1/files',
+            null,
+            array('Content-Type: multipart/form-data'),
+            true,
+            $this->fixture,
+            200,
+            Stripe::$apiUploadBase
+        );
+        $fp = fopen(dirname(__FILE__) . '/../data/test.png', 'r');
+        $resource = FileUpload::create(array(
+            "purpose" => "dispute_evidence",
+            "file" => $fp,
+        ));
+        $this->assertSame("Stripe\\FileUpload", get_class($resource));
+    }
+
+    public function testIsCreatableWithCurlFile()
+    {
+        if (!class_exists('\CurlFile', false)) {
+            // Older PHP versions don't support this
+            return;
+        }
+
+        $this->stubRequest(
+            'post',
+            '/v1/files',
+            null,
+            array('Content-Type: multipart/form-data'),
+            true,
+            $this->fixture,
+            200,
+            Stripe::$apiUploadBase
+        );
+        $curlFile = new \CurlFile(dirname(__FILE__) . '/../data/test.png');
+        $resource = FileUpload::create(array(
+            "purpose" => "dispute_evidence",
+            "file" => $curlFile,
+        ));
+        $this->assertSame("Stripe\\FileUpload", get_class($resource));
+    }
+}
diff --git a/tests/CurlClientTest.php b/tests/Stripe/HttpClient/CurlClientTest.php
similarity index 100%
rename from tests/CurlClientTest.php
rename to tests/Stripe/HttpClient/CurlClientTest.php
diff --git a/tests/Stripe/InvoiceItemTest.php b/tests/Stripe/InvoiceItemTest.php
new file mode 100644
index 000000000..daab5e9f3
--- /dev/null
+++ b/tests/Stripe/InvoiceItemTest.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Stripe;
+
+class InvoiceItemTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'ii_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/invoiceitems'
+        );
+        $resources = InvoiceItem::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/invoiceitems/' . self::TEST_RESOURCE_ID
+        );
+        $resource = InvoiceItem::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/invoiceitems'
+        );
+        $resource = InvoiceItem::create(array(
+            "amount" => 100,
+            "currency" => "usd",
+            "customer" => "cus_123"
+        ));
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = InvoiceItem::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/invoiceitems/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/invoiceitems/' . self::TEST_RESOURCE_ID
+        );
+        $resource = InvoiceItem::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $invoiceItem = InvoiceItem::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/invoiceitems/' . $invoiceItem->id
+        );
+        $resource = $invoiceItem->delete();
+        $this->assertSame("Stripe\\InvoiceItem", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/InvoiceTest.php b/tests/Stripe/InvoiceTest.php
new file mode 100644
index 000000000..30376b93d
--- /dev/null
+++ b/tests/Stripe/InvoiceTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Stripe;
+
+class InvoiceTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'in_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/invoices'
+        );
+        $resources = Invoice::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Invoice", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/invoices/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Invoice::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Invoice", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/invoices'
+        );
+        $resource = Invoice::create(array(
+            "customer" => "cus_123"
+        ));
+        $this->assertSame("Stripe\\Invoice", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Invoice::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/invoices/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Invoice", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/invoices/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Invoice::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Invoice", get_class($resource));
+    }
+
+    public function testCanRetrieveUpcoming()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/invoices/upcoming'
+        );
+        $resource = Invoice::upcoming(array("customer" => "cus_123"));
+        $this->assertSame("Stripe\\Invoice", get_class($resource));
+    }
+
+    public function testIsPayable()
+    {
+        $invoice = Invoice::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/invoices/' . $invoice->id . '/pay'
+        );
+        $resource = $invoice->pay();
+        $this->assertSame("Stripe\\Invoice", get_class($resource));
+        $this->assertSame($resource, $invoice);
+    }
+}
diff --git a/tests/OAuthTest.php b/tests/Stripe/OAuthTest.php
similarity index 95%
rename from tests/OAuthTest.php
rename to tests/Stripe/OAuthTest.php
index 732b6c5a7..e7593b3c7 100644
--- a/tests/OAuthTest.php
+++ b/tests/Stripe/OAuthTest.php
@@ -48,13 +48,15 @@ public function testAuthorizeUrl()
 
     public function testToken()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/oauth/token',
             array(
                 'grant_type' => 'authorization_code',
                 'code' => 'this_is_an_authorization_code',
             ),
+            null,
+            false,
             array(
                 'access_token' => 'sk_access_token',
                 'scope' => 'read_only',
@@ -77,13 +79,15 @@ public function testToken()
 
     public function testDeauthorize()
     {
-        $this->mockRequest(
+        $this->stubRequest(
             'POST',
             '/oauth/deauthorize',
             array(
-                'client_id' => 'ca_test',
                 'stripe_user_id' => 'acct_test_deauth',
+                'client_id' => 'ca_test',
             ),
+            null,
+            false,
             array(
                 'stripe_user_id' => 'acct_test_deauth',
             ),
diff --git a/tests/Stripe/OrderReturnTest.php b/tests/Stripe/OrderReturnTest.php
new file mode 100644
index 000000000..1ad09d790
--- /dev/null
+++ b/tests/Stripe/OrderReturnTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Stripe;
+
+class OrderReturnTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'orret_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/order_returns'
+        );
+        $resources = OrderReturn::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\OrderReturn", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/order_returns/' . self::TEST_RESOURCE_ID
+        );
+        $resource = OrderReturn::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\OrderReturn", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/OrderTest.php b/tests/Stripe/OrderTest.php
new file mode 100644
index 000000000..d231c7324
--- /dev/null
+++ b/tests/Stripe/OrderTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Stripe;
+
+class OrderTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'or_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/orders'
+        );
+        $resources = Order::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Order", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/orders/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Order::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Order", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/orders'
+        );
+        $resource = Order::create(array(
+            'currency' => 'usd'
+        ));
+        $this->assertSame("Stripe\\Order", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Order::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/orders/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Order", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/orders/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Order::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Order", get_class($resource));
+    }
+
+    public function testIsPayable()
+    {
+        $resource = Order::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/orders/' . self::TEST_RESOURCE_ID . '/pay'
+        );
+        $resource->pay();
+        $this->assertSame("Stripe\\Order", get_class($resource));
+    }
+
+    public function testIsReturnable()
+    {
+        $order = Order::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/orders/' . self::TEST_RESOURCE_ID . '/returns'
+        );
+        $resource = $order->returnOrder();
+        $this->assertSame("Stripe\\OrderReturn", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/PayoutTest.php b/tests/Stripe/PayoutTest.php
new file mode 100644
index 000000000..5fed2c5b3
--- /dev/null
+++ b/tests/Stripe/PayoutTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Stripe;
+
+class PayoutTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'po_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/payouts'
+        );
+        $resources = Payout::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Payout", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/payouts/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Payout::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Payout", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/payouts'
+        );
+        $resource = Payout::create(array(
+            "amount" => 100,
+            "currency" => "usd"
+        ));
+        $this->assertSame("Stripe\\Payout", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Payout::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/payouts/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Payout", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/payouts/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Payout::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Payout", get_class($resource));
+    }
+
+    public function testIsCancelable()
+    {
+        $resource = Payout::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/payouts/' . $resource->id . '/cancel'
+        );
+        $resource->cancel();
+        $this->assertSame("Stripe\\Payout", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/PlanTest.php b/tests/Stripe/PlanTest.php
new file mode 100644
index 000000000..2a70eac67
--- /dev/null
+++ b/tests/Stripe/PlanTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Stripe;
+
+class PlanTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'plan';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/plans'
+        );
+        $resources = Plan::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Plan", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/plans/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Plan::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Plan", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/plans'
+        );
+        $resource = Plan::create(array(
+            'amount' => 100,
+            'interval' => 'month',
+            'currency' => 'usd',
+            'name' => self::TEST_RESOURCE_ID,
+            'id' => self::TEST_RESOURCE_ID
+        ));
+        $this->assertSame("Stripe\\Plan", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Plan::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/plans/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Plan", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/plans/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Plan::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Plan", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = Plan::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/plans/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Plan", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/ProductTest.php b/tests/Stripe/ProductTest.php
new file mode 100644
index 000000000..68c8edd4c
--- /dev/null
+++ b/tests/Stripe/ProductTest.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Stripe;
+
+class ProductTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'prod_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/products'
+        );
+        $resources = Product::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Product", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/products/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Product::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Product", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/products'
+        );
+        $resource = Product::create(array(
+            'name' => 'name'
+        ));
+        $this->assertSame("Stripe\\Product", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Product::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/products/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Product", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/products/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Product::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Product", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = Product::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/products/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Product", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/RecipientTest.php b/tests/Stripe/RecipientTest.php
new file mode 100644
index 000000000..fc062d1c8
--- /dev/null
+++ b/tests/Stripe/RecipientTest.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Stripe;
+
+class RecipientTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'rp_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/recipients'
+        );
+        $resources = Recipient::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Recipient", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/recipients/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Recipient::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Recipient", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/recipients'
+        );
+        $resource = Recipient::create(array(
+            "name" => "name",
+            "type" => "individual"
+        ));
+        $this->assertSame("Stripe\\Recipient", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Recipient::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/recipients/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Recipient", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/recipients/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Recipient::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Recipient", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = Recipient::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/recipients/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Recipient", get_class($resource));
+    }
+
+    public function testCanListTransfers()
+    {
+        $recipient = Recipient::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'get',
+            '/v1/transfers',
+            array("recipient" => $recipient->id)
+        );
+        $resources = $recipient->transfers();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Transfer", get_class($resources->data[0]));
+    }
+}
diff --git a/tests/Stripe/RefundTest.php b/tests/Stripe/RefundTest.php
new file mode 100644
index 000000000..77511b17a
--- /dev/null
+++ b/tests/Stripe/RefundTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Stripe;
+
+class RefundTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 're_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/refunds'
+        );
+        $resources = Refund::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Refund", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/refunds/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Refund::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Refund", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/refunds'
+        );
+        $resource = Refund::create(array(
+            "charge" => "ch_123"
+        ));
+        $this->assertSame("Stripe\\Refund", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Refund::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/refunds/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Refund", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/refunds/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Refund::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Refund", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/SKUTest.php b/tests/Stripe/SKUTest.php
new file mode 100644
index 000000000..cc216b02b
--- /dev/null
+++ b/tests/Stripe/SKUTest.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Stripe;
+
+class SKUTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'sku_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/skus'
+        );
+        $resources = SKU::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\SKU", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/skus/' . self::TEST_RESOURCE_ID
+        );
+        $resource = SKU::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\SKU", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/skus'
+        );
+        $resource = SKU::create(array(
+            'currency'  => 'usd',
+            'inventory' => array(
+                'type'     => 'finite',
+                'quantity' => 1
+            ),
+            'price'     => 100,
+            'product'   => "prod_123"
+        ));
+        $this->assertSame("Stripe\\SKU", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = SKU::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/skus/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\SKU", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/skus/' . self::TEST_RESOURCE_ID
+        );
+        $resource = SKU::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\SKU", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = SKU::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/skus/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\SKU", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/SourceTest.php b/tests/Stripe/SourceTest.php
new file mode 100644
index 000000000..93bda3e0d
--- /dev/null
+++ b/tests/Stripe/SourceTest.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Stripe;
+
+class SourceTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'src_123';
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/sources/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Source::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Source", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/sources'
+        );
+        $resource = Source::create(array(
+            "type" => "card"
+        ));
+        $this->assertSame("Stripe\\Source", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Source::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/sources/' . self::TEST_RESOURCE_ID
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Source", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/sources/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Source::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Source", get_class($resource));
+    }
+
+    public function testCanSaveCardExpiryDate()
+    {
+        $response = array(
+            'id' => 'src_foo',
+            'object' => 'source',
+            'card' => array(
+                'exp_month' => 8,
+                'exp_year' => 2019,
+            ),
+        );
+        $source = Source::constructFrom(
+            $response,
+            new Util\RequestOptions()
+        );
+
+        $response['card']['exp_month'] = 12;
+        $response['card']['exp_year'] = 2022;
+        $this->stubRequest(
+            'POST',
+            '/v1/sources/src_foo',
+            array(
+                'card' => array(
+                    'exp_month' => 12,
+                    'exp_year' => 2022,
+                )
+            ),
+            null,
+            false,
+            $response
+        );
+
+        $source->card->exp_month = 12;
+        $source->card->exp_year = 2022;
+        $source->save();
+
+        $this->assertSame(12, $source->card->exp_month);
+        $this->assertSame(2022, $source->card->exp_year);
+    }
+
+    public function testIsDetachableWhenAttached()
+    {
+        $resource = Source::retrieve(self::TEST_RESOURCE_ID);
+        $resource->customer = "cus_123";
+        $this->expectsRequest(
+            'delete',
+            '/v1/customers/cus_123/sources/' . self::TEST_RESOURCE_ID
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\Source", get_class($resource));
+    }
+
+    /**
+     * @expectedException \Stripe\Error\Api
+     */
+    public function testIsNotDetachableWhenUnattached()
+    {
+        $resource = Source::retrieve(self::TEST_RESOURCE_ID);
+        $resource->detach();
+    }
+
+    public function testCanListSourceTransactions()
+    {
+        $source = Source::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'get',
+            '/v1/sources/' . $source->id . "/source_transactions"
+        );
+        $resources = $source->sourceTransactions();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\SourceTransaction", get_class($resources->data[0]));
+    }
+
+    public function testCanVerify()
+    {
+        $resource = Source::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/sources/' . self::TEST_RESOURCE_ID . "/verify"
+        );
+        $resource->verify(array("values" => array(32,45)));
+        $this->assertSame("Stripe\\Source", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/StripeObjectTest.php b/tests/Stripe/StripeObjectTest.php
new file mode 100644
index 000000000..7fcd9ab3d
--- /dev/null
+++ b/tests/Stripe/StripeObjectTest.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace Stripe;
+
+class StripeObjectTest extends TestCase
+{
+    public function testArrayAccessorsSemantics()
+    {
+        $s = new StripeObject();
+        $s['foo'] = 'a';
+        $this->assertSame($s['foo'], 'a');
+        $this->assertTrue(isset($s['foo']));
+        unset($s['foo']);
+        $this->assertFalse(isset($s['foo']));
+    }
+
+    public function testNormalAccessorsSemantics()
+    {
+        $s = new StripeObject();
+        $s->foo = 'a';
+        $this->assertSame($s->foo, 'a');
+        $this->assertTrue(isset($s->foo));
+        unset($s->foo);
+        $this->assertFalse(isset($s->foo));
+    }
+
+    public function testArrayAccessorsMatchNormalAccessors()
+    {
+        $s = new StripeObject();
+        $s->foo = 'a';
+        $this->assertSame($s['foo'], 'a');
+
+        $s['bar'] = 'b';
+        $this->assertSame($s->bar, 'b');
+    }
+
+    public function testKeys()
+    {
+        $s = new StripeObject();
+        $s->foo = 'a';
+        $this->assertSame($s->keys(), array('foo'));
+    }
+
+    public function testToArray()
+    {
+        $s = new StripeObject();
+        $s->foo = 'a';
+
+        $converted = $s->__toArray();
+
+        $this->assertInternalType('array', $converted);
+        $this->assertArrayHasKey('foo', $converted);
+        $this->assertEquals('a', $converted['foo']);
+    }
+
+    public function testRecursiveToArray()
+    {
+        $s = new StripeObject();
+        $z = new StripeObject();
+
+        $s->child = $z;
+        $z->foo = 'a';
+
+        $converted = $s->__toArray(true);
+
+        $this->assertInternalType('array', $converted);
+        $this->assertArrayHasKey('child', $converted);
+        $this->assertInternalType('array', $converted['child']);
+        $this->assertArrayHasKey('foo', $converted['child']);
+        $this->assertEquals('a', $converted['child']['foo']);
+    }
+
+    public function testNonexistentProperty()
+    {
+        $s = new StripeObject();
+        $this->assertNull($s->nonexistent);
+    }
+
+    public function testPropertyDoesNotExists()
+    {
+        $s = new StripeObject();
+        $this->assertNull($s['nonexistent']);
+    }
+
+    public function testJsonEncode()
+    {
+        // We can only JSON encode our objects in PHP 5.4+. 5.3 must use ->__toJSON()
+        if (version_compare(phpversion(), '5.4.0', '<')) {
+            return;
+        }
+
+        $s = new StripeObject();
+        $s->foo = 'a';
+
+        $this->assertEquals('{"foo":"a"}', json_encode($s->__toArray()));
+    }
+
+    public function testReplaceNewNestedUpdatable()
+    {
+        $s = new StripeObject();
+
+        $s->metadata = array('bar');
+        $this->assertSame($s->metadata, array('bar'));
+        $s->metadata = array('baz', 'qux');
+        $this->assertSame($s->metadata, array('baz', 'qux'));
+    }
+
+    public function testSerializeParametersEmptyObject()
+    {
+        $obj = new StripeObject();
+        $this->assertSame(array(), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnNewObjectWithSubObject()
+    {
+        $obj = new StripeObject();
+        $obj->metadata = array('foo' => 'bar');
+        $this->assertSame(array('metadata' => array('foo' => 'bar')), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnMoreComplexObject()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'metadata' => StripeObject::constructFrom(array(
+                'bar' => null,
+                'baz' => null,
+            ), new Util\RequestOptions()),
+        ), new Util\RequestOptions());
+        $obj->metadata->bar = 'newbar';
+        $this->assertSame(array('metadata' => array('bar' => 'newbar')), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnArray()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'foo' => null,
+        ), new Util\RequestOptions());
+        $obj->foo = array('new-value');
+        $this->assertSame(array('foo' => array('new-value')), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnArrayThatShortens()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'foo' => array('0-index', '1-index', '2-index'),
+        ), new Util\RequestOptions());
+        $obj->foo = array('new-value');
+        $this->assertSame(array('foo' => array('new-value')), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnArrayThatLengthens()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'foo' => array('0-index', '1-index', '2-index'),
+        ), new Util\RequestOptions());
+        $obj->foo = array_fill(0, 4, 'new-value');
+        $this->assertSame(array('foo' => array_fill(0, 4, 'new-value')), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnArrayOfHashes()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'additional_owners' => array(
+                StripeObject::constructFrom(array('bar' => null), new Util\RequestOptions())
+            ),
+        ), new Util\RequestOptions());
+        $obj->additional_owners[0]->bar = 'baz';
+        $this->assertSame(array('additional_owners' => array(array('bar' => 'baz'))), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersDoesNotIncludeUnchangedValues()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'foo' => null,
+        ), new Util\RequestOptions());
+        $this->assertSame(array(), $obj->serializeParameters());
+    }
+
+    public function testSerializeParametersOnReplacedAttachedObject()
+    {
+        $obj = StripeObject::constructFrom(array(
+            'metadata' => AttachedObject::constructFrom(array(
+                'bar' => 'foo',
+            ), new Util\RequestOptions()),
+        ), new Util\RequestOptions());
+        $obj->metadata = array('baz' => 'foo');
+        $this->assertSame(array('metadata' => array('bar' => '', 'baz' => 'foo')), $obj->serializeParameters());
+    }
+}
diff --git a/tests/Stripe/SubscriptionItemTest.php b/tests/Stripe/SubscriptionItemTest.php
new file mode 100644
index 000000000..ff879f5fb
--- /dev/null
+++ b/tests/Stripe/SubscriptionItemTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Stripe;
+
+class SubscriptionItemTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'si_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/subscription_items'
+        );
+        $resources = SubscriptionItem::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\SubscriptionItem", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/subscription_items/' . self::TEST_RESOURCE_ID
+        );
+        $resource = SubscriptionItem::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\SubscriptionItem", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/subscription_items'
+        );
+        $resource = SubscriptionItem::create(array(
+            "plan" => "plan",
+            "subscription" => "sub_123"
+        ));
+        $this->assertSame("Stripe\\SubscriptionItem", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = SubscriptionItem::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/subscription_items/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\SubscriptionItem", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/subscription_items/' . self::TEST_RESOURCE_ID
+        );
+        $resource = SubscriptionItem::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\SubscriptionItem", get_class($resource));
+    }
+
+    public function testIsDeletable()
+    {
+        $resource = SubscriptionItem::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/subscription_items/' . $resource->id
+        );
+        $resource->delete();
+        $this->assertSame("Stripe\\SubscriptionItem", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/SubscriptionTest.php b/tests/Stripe/SubscriptionTest.php
new file mode 100644
index 000000000..23260250e
--- /dev/null
+++ b/tests/Stripe/SubscriptionTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Stripe;
+
+class SubscriptionTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'sub_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/subscriptions'
+        );
+        $resources = Subscription::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Subscription", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/subscriptions/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Subscription::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/subscriptions'
+        );
+        $resource = Subscription::create(array(
+            "customer" => "cus_123",
+            "plan" => "plan"
+        ));
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Subscription::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/subscriptions/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/subscriptions/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Subscription::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+    }
+
+    public function testIsCancelable()
+    {
+        $resource = Subscription::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/subscriptions/' . $resource->id
+        );
+        $resource->cancel();
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+    }
+
+    public function testCanDeleteDiscount()
+    {
+        $resource = Subscription::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'delete',
+            '/v1/subscriptions/' . $resource->id . '/discount'
+        );
+        $resource->deleteDiscount();
+        $this->assertSame("Stripe\\Subscription", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/ThreeDSecureTest.php b/tests/Stripe/ThreeDSecureTest.php
new file mode 100644
index 000000000..5614e86d3
--- /dev/null
+++ b/tests/Stripe/ThreeDSecureTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Stripe;
+
+class ThreeDSecureTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'tdsrc_123';
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/3d_secure/' . self::TEST_RESOURCE_ID
+        );
+        $resource = ThreeDSecure::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\ThreeDSecure", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/3d_secure'
+        );
+        $resource = ThreeDSecure::create(array(
+            "amount" => 100,
+            "currency" => "usd",
+            "return_url" => "url"
+        ));
+        $this->assertSame("Stripe\\ThreeDSecure", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/TokenTest.php b/tests/Stripe/TokenTest.php
new file mode 100644
index 000000000..04b6f9960
--- /dev/null
+++ b/tests/Stripe/TokenTest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Stripe;
+
+class TokenTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'tok_123';
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/tokens/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Token::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Token", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/tokens'
+        );
+        $resource = Token::create(array("card" => "tok_visa"));
+        $this->assertSame("Stripe\\Token", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/TransferReversalTest.php b/tests/Stripe/TransferReversalTest.php
new file mode 100644
index 000000000..01ca8b052
--- /dev/null
+++ b/tests/Stripe/TransferReversalTest.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Stripe;
+
+class TransferReversalTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'trr_123';
+    const TEST_TRANSFER_ID = 'tr_123';
+
+    public function testIsSaveable()
+    {
+        $resource = Transfer::retrieveReversal(self::TEST_TRANSFER_ID, self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers/' . $resource->transfer . '/reversals/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\TransferReversal", get_class($resource));
+    }
+}
diff --git a/tests/Stripe/TransferTest.php b/tests/Stripe/TransferTest.php
new file mode 100644
index 000000000..3427bcf14
--- /dev/null
+++ b/tests/Stripe/TransferTest.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Stripe;
+
+class TransferTest extends TestCase
+{
+    const TEST_RESOURCE_ID = 'tr_123';
+    const TEST_REVERSAL_ID = 'trr_123';
+
+    public function testIsListable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/transfers'
+        );
+        $resources = Transfer::all();
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\Transfer", get_class($resources->data[0]));
+    }
+
+    public function testIsRetrievable()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/transfers/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Transfer::retrieve(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\Transfer", get_class($resource));
+    }
+
+    public function testIsCreatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers'
+        );
+        $resource = Transfer::create(array(
+            "amount" => 100,
+            "currency" => "usd",
+            "destination" => "acct_123"
+        ));
+        $this->assertSame("Stripe\\Transfer", get_class($resource));
+    }
+
+    public function testIsSaveable()
+    {
+        $resource = Transfer::retrieve(self::TEST_RESOURCE_ID);
+        $resource->metadata["key"] = "value";
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers/' . $resource->id
+        );
+        $resource->save();
+        $this->assertSame("Stripe\\Transfer", get_class($resource));
+    }
+
+    public function testIsUpdatable()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers/' . self::TEST_RESOURCE_ID
+        );
+        $resource = Transfer::update(self::TEST_RESOURCE_ID, array(
+            "metadata" => array("key" => "value"),
+        ));
+        $this->assertSame("Stripe\\Transfer", get_class($resource));
+    }
+
+    public function testIsReversable()
+    {
+        $resource = Transfer::retrieve(self::TEST_RESOURCE_ID);
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers/' . $resource->id . '/reversals'
+        );
+        $resource->reverse();
+        $this->assertSame("Stripe\\Transfer", get_class($resource));
+    }
+
+    public function testIsCancelable()
+    {
+        $transfer = Transfer::retrieve(self::TEST_RESOURCE_ID);
+
+        // stripe-mock does not support this anymore so we stub it
+        $this->stubRequest(
+            'post',
+            '/v1/transfers/' . $transfer->id . '/cancel'
+        );
+        $resource = $transfer->cancel();
+        $this->assertSame("Stripe\\Transfer", get_class($resource));
+        $this->assertSame($resource, $transfer);
+    }
+
+    public function testCanCreateReversal()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers/' . self::TEST_RESOURCE_ID . '/reversals'
+        );
+        $resource = Transfer::createReversal(self::TEST_RESOURCE_ID);
+        $this->assertSame("Stripe\\TransferReversal", get_class($resource));
+    }
+
+    public function testCanRetrieveReversal()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/transfers/' . self::TEST_RESOURCE_ID . '/reversals/' . self::TEST_REVERSAL_ID
+        );
+        $resource = Transfer::retrieveReversal(self::TEST_RESOURCE_ID, self::TEST_REVERSAL_ID);
+        $this->assertSame("Stripe\\TransferReversal", get_class($resource));
+    }
+
+    public function testCanUpdateReversal()
+    {
+        $this->expectsRequest(
+            'post',
+            '/v1/transfers/' . self::TEST_RESOURCE_ID . '/reversals/' . self::TEST_REVERSAL_ID
+        );
+        $resource = Transfer::updateReversal(
+            self::TEST_RESOURCE_ID,
+            self::TEST_REVERSAL_ID,
+            array(
+                "metadata" => array("key" => "value"),
+            )
+        );
+        $this->assertSame("Stripe\\TransferReversal", get_class($resource));
+    }
+
+    public function testCanListReversal()
+    {
+        $this->expectsRequest(
+            'get',
+            '/v1/transfers/' . self::TEST_RESOURCE_ID . '/reversals'
+        );
+        $resources = Transfer::allReversals(self::TEST_RESOURCE_ID);
+        $this->assertTrue(is_array($resources->data));
+        $this->assertSame("Stripe\\TransferReversal", get_class($resources->data[0]));
+    }
+}
diff --git a/tests/UtilDefaultLoggerTest.php b/tests/Stripe/Util/DefaultLoggerTest.php
similarity index 100%
rename from tests/UtilDefaultLoggerTest.php
rename to tests/Stripe/Util/DefaultLoggerTest.php
diff --git a/tests/RequestOptionsTest.php b/tests/Stripe/Util/RequestOptionsTest.php
similarity index 100%
rename from tests/RequestOptionsTest.php
rename to tests/Stripe/Util/RequestOptionsTest.php
diff --git a/tests/UtilTest.php b/tests/Stripe/Util/UtilTest.php
similarity index 94%
rename from tests/UtilTest.php
rename to tests/Stripe/Util/UtilTest.php
index 4cec5f6a4..437332bf9 100644
--- a/tests/UtilTest.php
+++ b/tests/Stripe/Util/UtilTest.php
@@ -24,7 +24,10 @@ public function testThatPHPHasValueSemanticsForArrays()
 
     public function testConvertStripeObjectToArrayIncludesId()
     {
-        $customer = self::createTestCustomer();
+        $customer = Util\Util::convertToStripeObject(array(
+            'id' => 'cus_123',
+            'object' => 'customer',
+        ), null);
         $this->assertTrue(array_key_exists("id", $customer->__toArray(true)));
     }
 
diff --git a/tests/WebhookTest.php b/tests/Stripe/WebhookTest.php
similarity index 100%
rename from tests/WebhookTest.php
rename to tests/Stripe/WebhookTest.php
diff --git a/tests/StripeObjectTest.php b/tests/StripeObjectTest.php
deleted file mode 100644
index 5f9222282..000000000
--- a/tests/StripeObjectTest.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class StripeObjectTest extends TestCase
-{
-    public function testArrayAccessorsSemantics()
-    {
-        $s = new StripeObject();
-        $s['foo'] = 'a';
-        $this->assertSame($s['foo'], 'a');
-        $this->assertTrue(isset($s['foo']));
-        unset($s['foo']);
-        $this->assertFalse(isset($s['foo']));
-    }
-
-    public function testNormalAccessorsSemantics()
-    {
-        $s = new StripeObject();
-        $s->foo = 'a';
-        $this->assertSame($s->foo, 'a');
-        $this->assertTrue(isset($s->foo));
-        unset($s->foo);
-        $this->assertFalse(isset($s->foo));
-    }
-
-    public function testArrayAccessorsMatchNormalAccessors()
-    {
-        $s = new StripeObject();
-        $s->foo = 'a';
-        $this->assertSame($s['foo'], 'a');
-
-        $s['bar'] = 'b';
-        $this->assertSame($s->bar, 'b');
-    }
-
-    public function testKeys()
-    {
-        $s = new StripeObject();
-        $s->foo = 'a';
-        $this->assertSame($s->keys(), array('foo'));
-    }
-
-    public function testToArray()
-    {
-        $s = new StripeObject();
-        $s->foo = 'a';
-
-        $converted = $s->__toArray();
-
-        $this->assertInternalType('array', $converted);
-        $this->assertArrayHasKey('foo', $converted);
-        $this->assertEquals('a', $converted['foo']);
-    }
-
-    public function testRecursiveToArray()
-    {
-        $s = new StripeObject();
-        $z = new StripeObject();
-
-        $s->child = $z;
-        $z->foo = 'a';
-
-        $converted = $s->__toArray(true);
-
-        $this->assertInternalType('array', $converted);
-        $this->assertArrayHasKey('child', $converted);
-        $this->assertInternalType('array', $converted['child']);
-        $this->assertArrayHasKey('foo', $converted['child']);
-        $this->assertEquals('a', $converted['child']['foo']);
-    }
-
-    public function testNonexistentProperty()
-    {
-        $s = new StripeObject();
-        $this->assertNull($s->nonexistent);
-    }
-
-    public function testPropertyDoesNotExists()
-    {
-        $s = new StripeObject();
-        $this->assertNull($s['nonexistent']);
-    }
-
-    public function testJsonEncode()
-    {
-        // We can only JSON encode our objects in PHP 5.4+. 5.3 must use ->__toJSON()
-        if (version_compare(phpversion(), '5.4.0', '<')) {
-            return;
-        }
-
-        $s = new StripeObject();
-        $s->foo = 'a';
-
-        $this->assertEquals('{"foo":"a"}', json_encode($s->__toArray()));
-    }
-
-    public function testReplaceNewNestedUpdatable()
-    {
-        StripeObject::init(); // Populate the $nestedUpdatableAttributes Set
-        $s = new StripeObject();
-
-        $s->metadata = array('bar');
-        $this->assertSame($s->metadata, array('bar'));
-        $s->metadata = array('baz', 'qux');
-        $this->assertSame($s->metadata, array('baz', 'qux'));
-    }
-}
diff --git a/tests/SubscriptionItemTest.php b/tests/SubscriptionItemTest.php
deleted file mode 100644
index 4e4a545a5..000000000
--- a/tests/SubscriptionItemTest.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class SubscriptionItemTest extends TestCase
-{
-    public function testCreateUpdateRetrieveListCancel()
-    {
-        $plan0ID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($plan0ID);
-
-        $customer = self::createTestCustomer();
-        $sub = Subscription::create(array('plan' => $plan0ID, 'customer' => $customer->id));
-
-        $plan1ID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($plan1ID);
-
-        $subItem = SubscriptionItem::create(array('plan' => $plan1ID, 'subscription' => $sub->id));
-        $this->assertSame($subItem->plan->id, $plan1ID);
-
-        $subItem->quantity = 2;
-        $subItem->save();
-
-        $subItem = SubscriptionItem::retrieve($subItem->id);
-        $this->assertSame($subItem->quantity, 2);
-
-        // Update the quantity parameter one more time
-        $subItem = SubscriptionItem::update($subItem->id, array('quantity' => 3));
-        $this->assertSame($subItem->quantity, 3);
-
-        $subItems = SubscriptionItem::all(array('subscription'=>$sub->id, 'limit'=>3));
-        $this->assertSame(get_class($subItems->data[0]), 'Stripe\SubscriptionItem');
-        $this->assertSame(2, count($subItems->data));
-
-        $subItem->delete();
-        $this->assertTrue($subItem->deleted);
-    }
-}
diff --git a/tests/SubscriptionTest.php b/tests/SubscriptionTest.php
deleted file mode 100644
index 08f741080..000000000
--- a/tests/SubscriptionTest.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class SubscriptionTest extends TestCase
-{
-
-    public function testCustomerCreateUpdateListCancel()
-    {
-        $planID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($planID);
-
-        $customer = self::createTestCustomer();
-
-        $sub = $customer->subscriptions->create(array('plan' => $planID));
-
-        $this->assertSame($sub->status, 'active');
-        $this->assertSame($sub->plan->id, $planID);
-
-        $sub->quantity = 2;
-        $sub->save();
-
-        $sub = $customer->subscriptions->retrieve($sub->id);
-        $this->assertSame($sub->status, 'active');
-        $this->assertSame($sub->plan->id, $planID);
-        $this->assertSame($sub->quantity, 2);
-
-        $subs = $customer->subscriptions->all(array('limit'=>3));
-        $this->assertSame(get_class($subs->data[0]), 'Stripe\Subscription');
-
-        $sub->cancel(array('at_period_end' => true));
-
-        $sub = $customer->subscriptions->retrieve($sub->id);
-        $this->assertSame($sub->status, 'active');
-        // @codingStandardsIgnoreStart
-        $this->assertTrue($sub->cancel_at_period_end);
-        // @codingStandardsIgnoreEnd
-    }
-
-    public function testCreateUpdateListCancel()
-    {
-        $planID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($planID);
-
-        $customer = self::createTestCustomer();
-
-        $sub = Subscription::create(array('plan' => $planID, 'customer' => $customer->id));
-
-        $this->assertSame($sub->status, 'active');
-        $this->assertSame($sub->plan->id, $planID);
-
-        $sub->quantity = 2;
-        $sub->save();
-
-        $sub = Subscription::retrieve($sub->id);
-        $this->assertSame($sub->status, 'active');
-        $this->assertSame($sub->plan->id, $planID);
-        $this->assertSame($sub->quantity, 2);
-
-        // Update the quantity parameter one more time
-        $sub = Subscription::update($sub->id, array("quantity" => 3));
-        $this->assertSame($sub->status, 'active');
-        $this->assertSame($sub->plan->id, $planID);
-        $this->assertSame($sub->quantity, 3);
-
-        $subs = Subscription::all(array('customer'=>$customer->id, 'plan'=>$planID, 'limit'=>3));
-        $this->assertSame(get_class($subs->data[0]), 'Stripe\Subscription');
-
-        $sub->cancel(array('at_period_end' => true));
-
-        $sub = Subscription::retrieve($sub->id);
-        $this->assertSame($sub->status, 'active');
-        $this->assertTrue($sub->cancel_at_period_end);
-    }
-
-    public function testCreateUpdateListCancelWithItems()
-    {
-        $plan0ID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($plan0ID);
-
-        $customer = self::createTestCustomer();
-
-        $sub = Subscription::create(array(
-          'customer' => $customer->id,
-          'items' => array(
-            array('plan' => $plan0ID),
-          ),
-        ));
-
-        $this->assertSame(count($sub->items->data), 1);
-        $this->assertSame($sub->items->data[0]->plan->id, $plan0ID);
-
-        $plan1ID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($plan1ID);
-
-        $sub = Subscription::update($sub->id, array(
-          'items' => array(
-            array('plan' => $plan1ID),
-          ),
-        ));
-
-        $this->assertSame(count($sub->items->data), 2);
-        $this->assertSame($sub->items->data[0]->plan->id, $plan0ID);
-        $this->assertSame($sub->items->data[1]->plan->id, $plan1ID);
-    }
-
-    public function testDeleteDiscount()
-    {
-        $planID = 'gold-' . self::generateRandomString(20);
-        self::retrieveOrCreatePlan($planID);
-
-        $couponID = '25off-' . self::generateRandomString(20);
-        self::retrieveOrCreateCoupon($couponID);
-
-        $customer = self::createTestCustomer();
-
-        $sub = $customer->subscriptions->create(
-            array(
-                'plan' => $planID,
-                'coupon' => $couponID
-            )
-        );
-
-        $this->assertSame($sub->status, 'active');
-        $this->assertSame($sub->plan->id, $planID);
-        $this->assertSame($sub->discount->coupon->id, $couponID);
-
-        $sub->deleteDiscount();
-        $sub = $customer->subscriptions->retrieve($sub->id);
-        $this->assertNull($sub->discount);
-    }
-}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 2fb972f47..cab2bf75b 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -3,261 +3,163 @@
 namespace Stripe;
 
 /**
- * Base class for Stripe test cases, provides some utility methods for creating
- * objects.
+ * Base class for Stripe test cases.
  */
 class TestCase extends \PHPUnit_Framework_TestCase
 {
-    const API_KEY = 'tGN0bIwXnHdwOa85VABjPdSn8nWY7G7I';
+    /** @var string original API base URL */
+    protected $origApiBase;
 
-    private $mock;
+    /** @var string original API key */
+    protected $origApiKey;
 
-    protected static function authorizeFromEnv()
-    {
-        $apiKey = getenv('STRIPE_API_KEY');
-        if (!$apiKey) {
-            $apiKey = self::API_KEY;
-        }
+    /** @var string original client ID */
+    protected $origClientId;
 
-        Stripe::setApiKey($apiKey);
-    }
+    /** @var object HTTP client mocker */
+    protected $clientMock;
 
     protected function setUp()
     {
-        ApiRequestor::setHttpClient(HttpClient\CurlClient::instance());
-
-        // Peg the API version so that it can be varied independently of the
-        // one set on the test account.
-        Stripe::setApiVersion('2017-04-06');
-
-        $this->mock = null;
-        $this->call = 0;
-    }
-
-    protected function mockRequest($method, $path, $params = array(), $return = array('id' => 'myId'), $rcode = 200, $base = 'https://api.stripe.com')
-    {
-        $mock = $this->setUpMockRequest();
-        $mock->expects($this->at($this->call++))
-             ->method('request')
-             ->with(strtolower($method), $base . $path, $this->anything(), $params, false)
-             ->willReturn(array(json_encode($return), $rcode, array()));
-    }
+        // Save original values so that we can restore them after running tests
+        $this->origApiBase = Stripe::$apiBase;
+        $this->origApiKey = Stripe::getApiKey();
+        $this->origClientId = Stripe::getClientId();
 
-    private function setUpMockRequest()
-    {
-        if (!$this->mock) {
-            self::authorizeFromEnv();
-            $this->mock = $this->getMock('\Stripe\HttpClient\ClientInterface');
-            ApiRequestor::setHttpClient($this->mock);
-        }
-        return $this->mock;
-    }
+        // Set up host and credentials for stripe-mock
+        Stripe::$apiBase = "http://localhost:" . MOCK_PORT;
+        Stripe::setApiKey("sk_test_123");
+        Stripe::setClientId("ca_123");
 
-    /**
-     * Create a valid test charge.
-     */
-    protected static function createTestCharge(array $attributes = array())
-    {
-        self::authorizeFromEnv();
+        // Set up the HTTP client mocker
+        $this->clientMock = $this->getMock('\Stripe\HttpClient\ClientInterface');
 
-        return Charge::create(
-            $attributes + array(
-                'amount' => 2000,
-                'currency' => 'usd',
-                'description' => 'Charge for test@example.com',
-                'card' => 'tok_visa',
-            )
-        );
+        // By default, use the real HTTP client
+        ApiRequestor::setHttpClient(HttpClient\CurlClient::instance());
     }
 
-    /**
-     * Create a valid test transfer.
-     */
-    protected static function createTestTransfer(array $attributes = array(), $opts = null)
+    protected function tearDown()
     {
-        self::authorizeFromEnv();
-
-        $recipient = self::createTestRecipient();
-
-        return Transfer::create(
-            $attributes + array(
-                'amount' => 2000,
-                'currency' => 'usd',
-                'description' => 'Transfer to test@example.com',
-                'recipient' => $recipient->id
-            ),
-            $opts
-        );
+        // Restore original values
+        Stripe::$apiBase = $this->origApiBase;
+        Stripe::setApiKey($this->origApiKey);
+        Stripe::setClientId($this->origClientId);
     }
 
     /**
-     * Create a valid test customer.
+     * Sets up a request expectation with the provided parameters. The request
+     * will actually go through and be emitted.
+     *
+     * @param string $method HTTP method (e.g. 'post', 'get', etc.)
+     * @param string $path relative path (e.g. '/v1/charges')
+     * @param array|null $params array of parameters. If null, parameters will
+     *   not be checked.
+     * @param string[]|null $headers array of headers. Does not need to be
+     *   exhaustive. If null, headers are not checked.
+     * @param bool $hasFile Whether the request parameters contains a file.
+     *   Defaults to false.
      */
-    protected static function createTestCustomer(array $attributes = array())
-    {
-        self::authorizeFromEnv();
-
-        return Customer::create(
-            $attributes + array(
-                'card' => 'tok_visa',
-            )
-        );
+    protected function expectsRequest(
+        $method,
+        $path,
+        $params = null,
+        $headers = null,
+        $hasFile = false
+    ) {
+        $this->prepareRequestMock($method, $path, $params, $headers, $hasFile)
+            ->will($this->returnCallback(
+                function ($method, $absUrl, $headers, $params, $hasFile) {
+                    $curlClient = HttpClient\CurlClient::instance();
+                    ApiRequestor::setHttpClient($curlClient);
+                    return $curlClient->request($method, $absUrl, $headers, $params, $hasFile);
+                }
+            ));
     }
 
     /**
-     * Create a valid test recipient
+     * Sets up a request expectation with the provided parameters. The request
+     * will not actually be emitted, instead the provided response parameters
+     * will be returned.
+     *
+     * @param string $method HTTP method (e.g. 'post', 'get', etc.)
+     * @param string $path relative path (e.g. '/v1/charges')
+     * @param array|null $params array of parameters. If null, parameters will
+     *   not be checked.
+     * @param string[]|null $headers array of headers. Does not need to be
+     *   exhaustive. If null, headers are not checked.
+     * @param bool $hasFile Whether the request parameters contains a file.
+     *   Defaults to false.
+     * @param array $response
+     * @param integer $rcode
+     * @param string|null $base
+     *
+     * @return array
      */
-    protected static function createTestRecipient(array $attributes = array())
-    {
-        self::authorizeFromEnv();
-
-        return Recipient::create(
-            $attributes + array(
-                'name' => 'PHP Test',
-                'type' => 'individual',
-                'tax_id' => '000000000',
-                'bank_account' => array(
-                    'country' => 'US',
-                    'routing_number' => '110000000',
-                    'account_number' => '000123456789'
-                ),
-            )
-        );
-    }
-
-    /**
-     * Create a test account
-     */
-    protected static function createTestAccount(array $attributes = array())
-    {
-        self::authorizeFromEnv();
-
-        return Account::create(
-            $attributes + array(
-                'managed' => false,
-                'country' => 'US',
-                'email' => self::generateRandomEmail(),
-            )
-        );
+    protected function stubRequest(
+        $method,
+        $path,
+        $params = null,
+        $headers = null,
+        $hasFile = false,
+        $response = array(),
+        $rcode = 200,
+        $base = null
+    ) {
+        $this->prepareRequestMock($method, $path, $params, $headers, $hasFile, $base)
+            ->willReturn(array(json_encode($response), $rcode, array()));
     }
 
     /**
-     * Create a test account
+     * Prepares the client mocker for an invocation of the `request` method.
+     * This helper method is used by both `expectsRequest` and `stubRequest` to
+     * prepare the client mocker to expect an invocation of the `request` method
+     * with the provided arguments.
+     *
+     * @param string $method HTTP method (e.g. 'post', 'get', etc.)
+     * @param string $path relative path (e.g. '/v1/charges')
+     * @param array|null $params array of parameters. If null, parameters will
+     *   not be checked.
+     * @param string[]|null $headers array of headers. Does not need to be
+     *   exhaustive. If null, headers are not checked.
+     * @param bool $hasFile Whether the request parameters contains a file.
+     *   Defaults to false.
+     * @param string|null $base base URL (e.g. 'https://api.stripe.com')
+     *
+     * @return PHPUnit_Framework_MockObject_Builder_InvocationMocker
      */
-    protected static function createTestManagedAccount(array $attributes = array())
-    {
-        self::authorizeFromEnv();
-
-        return Account::create(
-            $attributes + array(
-                'managed' => true,
-                'country' => 'US',
-                'external_account' => array(
-                    'object' => 'bank_account',
-                    'country' => 'US',
-                    'currency' => 'usd',
-                    'routing_number' => '110000000',
-                    'account_number' => '000123456789'
-                ),
-                'legal_entity' => array(
-                    'type'               => 'individual',
-                    'personal_id_number' => '000000000',
-                    'type'               => 'individual',
-                    'dob'                => array('year' => '1980', 'month' => '01', 'day' => '01'),
-                    'first_name'         => 'John',
-                    'last_name'          => 'Doe',
-                    'address'            => array(
-                        'line1'          => '1234 Main Street',
-                        'postal_code'    => '94110',
-                        'city'           => 'San Francisco'
-                    ),
-                    'personal_address'   => array(
-                        'line1'          => '1234 Main Street',
-                        'postal_code'    => '94110',
-                        'city'           => 'San Francisco'
-                    )
-                ),
-                'tos_acceptance' => array('date' => time(), 'ip' => '127.0.0.1')
-            )
-        );
-    }
-
-    /**
-     * Verify that a plan with a given ID exists, or create a new one if it does
-     * not.
-     */
-    protected static function retrieveOrCreatePlan($id)
-    {
-        self::authorizeFromEnv();
-
-        try {
-            $plan = Plan::retrieve($id);
-        } catch (Error\InvalidRequest $exception) {
-            $plan = Plan::create(
-                array(
-                    'id' => $id,
-                    'amount' => 0,
-                    'currency' => 'usd',
-                    'interval' => 'month',
-                    'name' => 'Gold Test Plan',
-                )
-            );
+    private function prepareRequestMock(
+        $method,
+        $path,
+        $params = null,
+        $headers = null,
+        $hasFile = false,
+        $base = null
+    ) {
+        ApiRequestor::setHttpClient($this->clientMock);
+
+        if ($base === null) {
+            $base = Stripe::$apiBase;
         }
-    }
-
-    /**
-     * Verify that a coupon with a given ID exists, or create a new one if it
-     * does not.
-     */
-    protected static function retrieveOrCreateCoupon($id)
-    {
-        self::authorizeFromEnv();
-
-        try {
-            $coupon = Coupon::retrieve($id);
-        } catch (Error\InvalidRequest $exception) {
-            $coupon = Coupon::create(
-                array(
-                    'id' => $id,
-                    'duration' => 'forever',
-                    'percent_off' => 25,
-                )
+        $absUrl = $base . $path;
+
+        return $this->clientMock
+            ->expects($this->once())
+            ->method('request')
+            ->with(
+                $this->identicalTo(strtolower($method)),
+                $this->identicalTo($absUrl),
+                // for headers, we only check that all of the headers provided in $headers are
+                // present in the list of headers of the actual request
+                $headers === null ? $this->anything() : $this->callback(function ($array) use ($headers) {
+                    foreach ($headers as $header) {
+                        if (!in_array($header, $array)) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }),
+                $params === null ? $this->anything() : $this->identicalTo($params),
+                $this->identicalTo($hasFile)
             );
-        }
-    }
-
-    /**
-     * Generate a semi-random string
-     */
-    protected static function generateRandomString($length = 24)
-    {
-        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU';
-        $charactersLength = strlen($characters);
-        $randomString = '';
-        for ($i = 0; $i < $length; $i++) {
-            $randomString .= $characters[rand(0, $charactersLength - 1)];
-        }
-        return $randomString;
-    }
-
-    /**
-     * Generate a semi-random email.
-     */
-    protected static function generateRandomEmail()
-    {
-        return 'dev-platform-bots+php-'.self::generateRandomString(12).'@stripe.com';
-    }
-
-    protected static function createTestBitcoinReceiver($email)
-    {
-        $receiver = BitcoinReceiver::create(
-            array(
-                'amount' => 100,
-                'currency' => 'usd',
-                'description' => 'some details',
-                'email' => $email
-            )
-        );
-        return $receiver;
     }
 }
diff --git a/tests/ThreeDSecureTest.php b/tests/ThreeDSecureTest.php
deleted file mode 100644
index ebdd62335..000000000
--- a/tests/ThreeDSecureTest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class ThreeDSecureTest extends TestCase
-{
-    public function testRetrieve()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/3d_secure/tdsrc_test',
-            array(),
-            array(
-                'id' => 'tdsrc_test',
-                'object' => 'three_d_secure'
-            )
-        );
-        $three_d_secure = ThreeDSecure::retrieve('tdsrc_test');
-        $this->assertSame($three_d_secure->id, 'tdsrc_test');
-    }
-
-    public function testCreate()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/3d_secure',
-            array(
-                'card' => 'tok_test',
-                'amount' => 1500,
-                'currency' => 'usd',
-                'return_url' => 'https://example.org/3d-secure-result'
-            ),
-            array(
-                'id' => 'tdsrc_test',
-                'object' => 'three_d_secure'
-            )
-        );
-        $three_d_secure = ThreeDSecure::create(array(
-                'card' => 'tok_test',
-                'amount' => 1500,
-                'currency' => 'usd',
-                'return_url' => 'https://example.org/3d-secure-result'
-        ));
-        $this->assertSame($three_d_secure->id, 'tdsrc_test');
-    }
-}
diff --git a/tests/TokenTest.php b/tests/TokenTest.php
deleted file mode 100644
index 60ec76a15..000000000
--- a/tests/TokenTest.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class TokenTest extends TestCase
-{
-    public function testUrls()
-    {
-        $this->assertSame(Token::classUrl(), '/v1/tokens');
-        $token = new Token('abcd/efgh');
-        $this->assertSame($token->instanceUrl(), '/v1/tokens/abcd%2Fefgh');
-    }
-}
diff --git a/tests/TransferReversalTest.php b/tests/TransferReversalTest.php
deleted file mode 100644
index 86d746650..000000000
--- a/tests/TransferReversalTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class TransferReversalTest extends TestCase
-{
-    // The resource that was traditionally called "transfer" became a "payout"
-    // in API version 2017-04-06. We're testing traditional transfers here, so
-    // we force the API version just prior anywhere that we need to.
-    private $opts = array('stripe_version' => '2017-02-14');
-
-    public function testList()
-    {
-        $transfer = self::createTestTransfer(array(), $this->opts);
-        $all = $transfer->reversals->all();
-        $this->assertSame(false, $all['has_more']);
-        $this->assertSame(0, count($all->data));
-    }
-}
diff --git a/tests/TransferTest.php b/tests/TransferTest.php
deleted file mode 100644
index 298e3c293..000000000
--- a/tests/TransferTest.php
+++ /dev/null
@@ -1,117 +0,0 @@
-<?php
-
-namespace Stripe;
-
-class TransferTest extends TestCase
-{
-    // The resource that was traditionally called "transfer" became a "payout"
-    // in API version 2017-04-06. We're testing traditional transfers here, so
-    // we force the API version just prior anywhere that we need to.
-    private $opts = array('stripe_version' => '2017-02-14');
-
-    public function testCreate()
-    {
-        $transfer = self::createTestTransfer(array(), $this->opts);
-        $this->assertSame('transfer', $transfer->object);
-    }
-
-    public function testRetrieve()
-    {
-        $transfer = self::createTestTransfer(array(), $this->opts);
-        $reloaded = Transfer::retrieve($transfer->id, $this->opts);
-        $this->assertSame($reloaded->id, $transfer->id);
-    }
-
-    public function testTransferUpdateMetadata()
-    {
-        $transfer = self::createTestTransfer(array(), $this->opts);
-
-        $transfer->metadata['test'] = 'foo bar';
-        $transfer->save();
-
-        $updatedTransfer = Transfer::retrieve($transfer->id, $this->opts);
-        $this->assertSame('foo bar', $updatedTransfer->metadata['test']);
-    }
-
-    public function testTransferUpdateMetadataAll()
-    {
-        $transfer = self::createTestTransfer(array(), $this->opts);
-
-        $transfer->metadata = array('test' => 'foo bar');
-        $transfer->save();
-
-        $updatedTransfer = Transfer::retrieve($transfer->id, $this->opts);
-        $this->assertSame('foo bar', $updatedTransfer->metadata['test']);
-    }
-
-    public function testStaticCreateReversal()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/transfers/tr_123/reversals',
-            array(),
-            array('id' => 'trr_123', 'object' => 'transfer_reversal')
-        );
-
-        $reversal = Transfer::createReversal(
-            'tr_123'
-        );
-
-        $this->assertSame('trr_123', $reversal->id);
-        $this->assertSame('transfer_reversal', $reversal->object);
-    }
-
-    public function testStaticRetrieveReversal()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/transfers/tr_123/reversals/trr_123',
-            array(),
-            array('id' => 'trr_123', 'object' => 'transfer_reversal')
-        );
-
-        $reversal = Transfer::retrieveReversal(
-            'tr_123',
-            'trr_123'
-        );
-
-        $this->assertSame('trr_123', $reversal->id);
-        $this->assertSame('transfer_reversal', $reversal->object);
-    }
-
-    public function testStaticUpdateReversal()
-    {
-        $this->mockRequest(
-            'POST',
-            '/v1/transfers/tr_123/reversals/trr_123',
-            array('metadata' => array('foo' => 'bar')),
-            array('id' => 'trr_123', 'object' => 'transfer_reversal')
-        );
-
-        $reversal = Transfer::updateReversal(
-            'tr_123',
-            'trr_123',
-            array('metadata' => array('foo' => 'bar'))
-        );
-
-        $this->assertSame('trr_123', $reversal->id);
-        $this->assertSame('transfer_reversal', $reversal->object);
-    }
-
-    public function testStaticAllReversals()
-    {
-        $this->mockRequest(
-            'GET',
-            '/v1/transfers/tr_123/reversals',
-            array(),
-            array('object' => 'list', 'data' => array())
-        );
-
-        $reversals = Transfer::allReversals(
-            'tr_123'
-        );
-
-        $this->assertSame('list', $reversals->object);
-        $this->assertEmpty($reversals->data);
-    }
-}
diff --git a/tests/bootstrap.no_autoload.php b/tests/bootstrap.no_autoload.php
index 735805878..7011a3f47 100644
--- a/tests/bootstrap.no_autoload.php
+++ b/tests/bootstrap.no_autoload.php
@@ -1,4 +1,5 @@
 <?php
 
 require_once __DIR__ . '/../init.php';
-require_once __DIR__ . '/TestCase.php';
+
+require_once __DIR__ . '/bootstrap.php';
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index de6ff4e25..470b70278 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,3 +1,43 @@
 <?php
 
+define("MOCK_MINIMUM_VERSION", "0.5.0");
+define("MOCK_PORT", getenv("STRIPE_MOCK_PORT") ?: 12111);
+
+// Send a request to stripe-mock
+$ch = curl_init("http://localhost:" . MOCK_PORT . "/");
+curl_setopt($ch, CURLOPT_HEADER, 1);
+curl_setopt($ch, CURLOPT_NOBODY, 1);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+$resp = curl_exec($ch);
+
+if (curl_errno($ch)) {
+    echo "Couldn't reach stripe-mock at `localhost:" . MOCK_PORT . "`. Is " .
+         "it running? Please see README for setup instructions.\n";
+    exit(1);
+}
+
+// Retrieve the Stripe-Mock-Version header
+$version = null;
+$headers = explode("\n", $resp);
+foreach ($headers as $header) {
+    $pair = explode(":", $header, 2);
+    if ($pair[0] == "Stripe-Mock-Version") {
+        $version = trim($pair[1]);
+    }
+}
+
+if ($version === null) {
+    echo "Could not retrieve Stripe-Mock-Version header. Are you sure " .
+         "that the server at `localhost:" . MOCK_PORT . "` is a stripe-mock " .
+         "instance?";
+    exit(1);
+}
+
+if (version_compare($version, MOCK_MINIMUM_VERSION) == -1) {
+    echo "Your version of stripe-mock (" . $version . ") is too old. The minimum " .
+         "version to run this test suite is " . MOCK_MINIMUM_VERSION . ". " .
+         "Please see its repository for upgrade instructions.\n";
+    exit(1);
+}
+
 require_once __DIR__ . '/TestCase.php';
diff --git a/data/test.png b/tests/data/test.png
similarity index 100%
rename from data/test.png
rename to tests/data/test.png