diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
index 2d45275..c9532f4 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
@@ -34,7 +34,7 @@
  *
  * @ingroup entity_api
  */
-class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface {
+class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface, EntityTypeListenerInterface {
 
   /**
    * The mapping of field columns to SQL tables.
@@ -218,13 +218,6 @@ public function getRevisionDataTable() {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function getSchema() {
-    return $this->schemaHandler()->getSchema();
-  }
-
-  /**
    * Gets the schema handler for this entity storage.
    *
    * @return \Drupal\Core\Entity\Schema\SqlContentEntityStorageSchema
@@ -233,7 +226,7 @@ public function getSchema() {
   protected function schemaHandler() {
     if (!isset($this->schemaHandler)) {
       $schema_handler_class = $this->entityType->getHandlerClass('storage_schema') ?: 'Drupal\Core\Entity\Schema\SqlContentEntityStorageSchema';
-      $this->schemaHandler = new $schema_handler_class($this->entityManager, $this->entityType, $this);
+      $this->schemaHandler = new $schema_handler_class($this->entityManager, $this->entityType, $this, $this->database);
     }
     return $this->schemaHandler;
   }
@@ -1368,6 +1361,27 @@ protected function usesDedicatedTable(FieldStorageDefinitionInterface $definitio
   /**
    * {@inheritdoc}
    */
+  public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
+    $this->schemaHandler()->onEntityTypeCreate($entity_type);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    $this->schemaHandler()->onEntityTypeUpdate($entity_type, $original);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+    $this->schemaHandler()->onEntityTypeDelete($entity_type);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
     $schema = $this->_fieldSqlSchema($storage_definition);
     foreach ($schema as $name => $table) {
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 26eac4e..746b253 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -958,6 +958,42 @@ public function getEntityTypeFromClass($class_name) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
+    // @todo Forward this to all interested handlers, not only storage, once
+    //   iterating handlers is possible: https://www.drupal.org/node/2332857.
+    $storage = $this->getStorage($entity_type->id());
+    if ($storage instanceof EntityTypeListenerInterface) {
+      $storage->onEntityTypeCreate($entity_type);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    // @todo Forward this to all interested handlers, not only storage, once
+    //   iterating handlers is possible: https://www.drupal.org/node/2332857.
+    $storage = $this->getStorage($entity_type->id());
+    if ($storage instanceof EntityTypeListenerInterface) {
+      $storage->onEntityTypeUpdate($entity_type, $original);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+    // @todo Forward this to all interested handlers, not only storage, once
+    //   iterating handlers is possible: https://www.drupal.org/node/2332857.
+    $storage = $this->getStorage($entity_type->id());
+    if ($storage instanceof EntityTypeListenerInterface) {
+      $storage->onEntityTypeDelete($entity_type);
+    }
+  }
+
+  /**
    * Acts on entity bundle rename.
    *
    * @param string $entity_type_id
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index 5bf03cc..bf52202 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -12,7 +12,7 @@
 /**
  * Provides an interface for entity type managers.
  */
-interface EntityManagerInterface extends PluginManagerInterface {
+interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface {
 
   /**
    * Builds a list of entity type labels suitable for a Form API options list.
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php
new file mode 100644
index 0000000..c42667c
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityTypeListenerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Defines an interface for reacting to entity type creation, deletion, and updates.
+ *
+ * @todo Convert to Symfony events: https://www.drupal.org/node/2332935
+ */
+interface EntityTypeListenerInterface {
+
+  /**
+   * Reacts to the creation of the entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type being created.
+   */
+  public function onEntityTypeCreate(EntityTypeInterface $entity_type);
+
+  /**
+   * Reacts to the update of the entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The updated entity type definition.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $original
+   *   The original entity type definition.
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original);
+
+  /**
+   * Reacts to the deletion of the entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type being deleted.
+   */
+  public function onEntityTypeDelete(EntityTypeInterface $entity_type);
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php b/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php
index a38b82c..51001bd 100644
--- a/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php
+++ b/core/lib/Drupal/Core/Entity/Schema/EntitySchemaHandlerInterface.php
@@ -6,9 +6,10 @@
  */
 
 namespace Drupal\Core\Entity\Schema;
+use Drupal\Core\Entity\EntityTypeListenerInterface;
 
 /**
  * Defines an interface for handling the storage schema of entities.
  */
-interface EntitySchemaHandlerInterface extends EntitySchemaProviderInterface {
+interface EntitySchemaHandlerInterface extends EntityTypeListenerInterface {
 }
diff --git a/core/lib/Drupal/Core/Entity/Schema/EntitySchemaProviderInterface.php b/core/lib/Drupal/Core/Entity/Schema/EntitySchemaProviderInterface.php
deleted file mode 100644
index c976782..0000000
--- a/core/lib/Drupal/Core/Entity/Schema/EntitySchemaProviderInterface.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Entity\Schema\EntitySchemaProviderInterface.
- */
-
-namespace Drupal\Core\Entity\Schema;
-
-/**
- * Defines a common interface to return the storage schema for entities.
- */
-interface EntitySchemaProviderInterface {
-
-  /**
-   * Gets the full schema array for a given entity type.
-   *
-   * @return array
-   *   A schema array for the entity type's tables.
-   */
-  public function getSchema();
-
-}
diff --git a/core/lib/Drupal/Core/Entity/Schema/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Schema/SqlContentEntityStorageSchema.php
index 830d2ab..4fe6c7e 100644
--- a/core/lib/Drupal/Core/Entity/Schema/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Schema/SqlContentEntityStorageSchema.php
@@ -7,9 +7,11 @@
 
 namespace Drupal\Core\Entity\Schema;
 
+use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\ContentEntityDatabaseStorage;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 
 /**
  * Defines a schema handler that supports revisionable, translatable entities.
@@ -45,6 +47,13 @@ class SqlContentEntityStorageSchema implements EntitySchemaHandlerInterface {
   protected $schema;
 
   /**
+   * The database connection to be used.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
    * Constructs a SqlContentEntityStorageSchema.
    *
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
@@ -53,18 +62,52 @@ class SqlContentEntityStorageSchema implements EntitySchemaHandlerInterface {
    *   The entity type.
    * @param \Drupal\Core\Entity\ContentEntityDatabaseStorage $storage
    *   The storage of the entity type. This must be an SQL-based storage.
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection to be used.
    */
-  public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorage $storage) {
+  public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorage $storage, Connection $database) {
     $this->entityType = $entity_type;
     $this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id());
     $this->storage = $storage;
+    $this->database = $database;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getSchema() {
-    return $this->getEntitySchema($this->entityType);
+  public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
+    $schema_handler = $this->database->schema();
+    $schema = $this->getEntitySchema($entity_type, TRUE);
+    foreach ($schema as $table_name => $table_schema) {
+      if (!$schema_handler->tableExists($table_name)) {
+        $schema_handler->createTable($table_name, $table_schema);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
+    // @todo Implement proper updates: https://www.drupal.org/node/1498720.
+    //   Meanwhile, treat a change from non-Sql storage to Sql storage as
+    //   identical to creation with respect to Sql schema handling.
+    if (!is_subclass_of($original->getStorageClass(), '\Drupal\Core\Entity\Sql\SqlEntityStorageInterface')) {
+      $this->onEntityTypeCreate($entity_type);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
+    $schema_handler = $this->database->schema();
+    $schema = $this->getEntitySchema($entity_type, TRUE);
+    foreach ($schema as $table_name => $table_schema) {
+      if ($schema_handler->tableExists($table_name)) {
+        $schema_handler->dropTable($table_name);
+      }
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php
index 107ed26..b0a9b05 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php
@@ -8,12 +8,11 @@
 namespace Drupal\Core\Entity\Sql;
 
 use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface;
 
 /**
  * A common interface for SQL-based entity storage implementations.
  */
-interface SqlEntityStorageInterface extends EntityStorageInterface, EntitySchemaProviderInterface {
+interface SqlEntityStorageInterface extends EntityStorageInterface {
 
   /**
    * Gets a table mapping for the entity's SQL tables.
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index aa36d42..f4c0170 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -12,7 +12,6 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -845,19 +844,14 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         }
         drupal_set_installed_schema_version($module, $version);
 
-        // Install any entity schemas belonging to the module.
+        // Notify the entity manager that this module's entity types are new,
+        // so that it can notify all interested handlers. For example, a
+        // SQL-based storage handler can use this as an opportunity to create
+        // the necessary database tables.
         $entity_manager = \Drupal::entityManager();
-        $schema = \Drupal::database()->schema();
         foreach ($entity_manager->getDefinitions() as $entity_type) {
           if ($entity_type->getProvider() == $module) {
-            $storage = $entity_manager->getStorage($entity_type->id());
-            if ($storage instanceof EntitySchemaProviderInterface) {
-              foreach ($storage->getSchema() as $table_name => $table_schema) {
-                if (!$schema->tableExists($table_name)) {
-                  $schema->createTable($table_name, $table_schema);
-                }
-              }
-            }
+            $entity_manager->onEntityTypeCreate($entity_type);
           }
         }
 
@@ -965,19 +959,13 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
       // Remove all configuration belonging to the module.
       \Drupal::service('config.manager')->uninstall('module', $module);
 
-      // Remove any entity schemas belonging to the module.
-
-      $schema = \Drupal::database()->schema();
+      // Notify the entity manager that this module's entity types are being
+      // deleted, so that it can notify all interested handlers. For example,
+      // a SQL-based storage handler can use this as an opportunity to drop
+      // the corresponding database tables.
       foreach ($entity_manager->getDefinitions() as $entity_type) {
         if ($entity_type->getProvider() == $module) {
-          $storage = $entity_manager->getStorage($entity_type->id());
-          if ($storage instanceof EntitySchemaProviderInterface) {
-            foreach ($storage->getSchema() as $table_name => $table_schema) {
-              if ($schema->tableExists($table_name)) {
-                $schema->dropTable($table_name);
-              }
-            }
-          }
+          $entity_manager->onEntityTypeDelete($entity_type);
         }
       }
 
diff --git a/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install b/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install
index bcbe5df..c56d5b5 100644
--- a/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install
+++ b/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install
@@ -9,16 +9,15 @@
  * Implements hook_install().
  */
 function contact_storage_test_install() {
-  // ModuleHandler won't create the schema automatically because Message entity
-  // belongs to contact.module.
-  // @todo Remove this when https://www.drupal.org/node/1498720 is in.
   $entity_manager = \Drupal::entityManager();
-  $schema = \Drupal::database()->schema();
   $entity_type = $entity_manager->getDefinition('contact_message');
-  $storage = $entity_manager->getStorage($entity_type->id());
-  foreach ($storage->getSchema() as $table_name => $table_schema) {
-    if (!$schema->tableExists($table_name)) {
-      $schema->createTable($table_name, $table_schema);
-    }
-  }
+
+  // Recreate the original entity type definition, in order to notify the
+  // manager of what changed. The change of storage backend will trigger
+  // schema installation.
+  // @see contact_storage_test_entity_type_alter()
+  $original = clone $entity_type;
+  $original->setStorageClass('Drupal\Core\Entity\ContentEntityNullStorage');
+
+  $entity_manager->onEntityTypeUpdate($entity_type, $original);
 }
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 4ef5e10..fe39d46 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -11,10 +11,11 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DrupalKernel;
+use Drupal\Core\Entity\EntityTypeListenerInterface;
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
 use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Site\Settings;
-use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface;
 use Symfony\Component\DependencyInjection\Parameter;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\HttpFoundation\Request;
@@ -377,36 +378,39 @@ protected function installSchema($module, $tables) {
 
 
   /**
-   * Installs the tables for a specific entity type.
+   * Installs the storage schema for a specific entity type.
    *
    * @param string $entity_type_id
    *   The ID of the entity type.
-   *
-   * @throws \RuntimeException
-   *   Thrown when the entity type does not support automatic schema installation.
    */
   protected function installEntitySchema($entity_type_id) {
     /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
     $entity_manager = $this->container->get('entity.manager');
-    /** @var \Drupal\Core\Database\Schema $schema_handler */
-    $schema_handler = $this->container->get('database')->schema();
+    $entity_type = $entity_manager->getDefinition($entity_type_id);
+    $entity_manager->onEntityTypeCreate($entity_type);
 
+    // For test runs, the most common storage backend is a SQL database. For
+    // this case, ensure the tables got created.
     $storage = $entity_manager->getStorage($entity_type_id);
-    if ($storage instanceof EntitySchemaProviderInterface) {
-      $schema = $storage->getSchema();
-      foreach ($schema as $table_name => $table_schema) {
-        $schema_handler->createTable($table_name, $table_schema);
+    if ($storage instanceof SqlEntityStorageInterface) {
+      $tables = $storage->getTableMapping()->getTableNames();
+      $db_schema = $this->container->get('database')->schema();
+      $all_tables_exist = TRUE;
+      foreach ($tables as $table) {
+        if (!$db_schema->tableExists($table)) {
+          $this->fail(String::format('Installed entity type table for the %entity_type entity type: %table', array(
+            '%entity_type' => $entity_type_id,
+            '%table' => $table,
+          )));
+          $all_tables_exist = FALSE;
+        }
+      }
+      if ($all_tables_exist) {
+        $this->pass(String::format('Installed entity type tables for the %entity_type entity type: %tables', array(
+          '%entity_type' => $entity_type_id,
+          '%tables' => '{' . implode('}, {', $tables) . '}',
+        )));
       }
-
-      $this->pass(String::format('Installed entity type tables for the %entity_type entity type: %tables', array(
-        '%entity_type' => $entity_type_id,
-        '%tables' => '{' . implode('}, {', array_keys($schema)) . '}',
-      )));
-    }
-    else {
-      throw new \RuntimeException(String::format('Entity type %entity_type does not support automatic schema installation.', array(
-        '%entity-type' => $entity_type_id,
-      )));
     }
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
index 51ac5df..fa82039 100644
--- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\ContentEntityDatabaseStorage;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\Schema\SqlContentEntityStorageSchema;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Language\Language;
 use Drupal\Tests\UnitTestCase;
@@ -266,21 +267,23 @@ public function providerTestGetRevisionDataTable() {
   }
 
   /**
-   * Tests ContentEntityDatabaseStorage::getSchema().
+   * Tests ContentEntityDatabaseStorage::onEntityTypeCreate().
    *
    * @covers ::__construct()
-   * @covers ::getSchema()
-   * @covers ::schemaHandler()
+   * @covers ::onEntityTypeCreate()
    * @covers ::getTableMapping()
    */
-  public function testGetSchema() {
+  public function testOnEntityTypeCreate() {
     $columns = array(
       'value' => array(
         'type' => 'int',
       ),
     );
 
-    $this->fieldDefinitions['id'] = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $this->fieldDefinitions['id'] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
+    $this->fieldDefinitions['id']->expects($this->any())
+      ->method('getName')
+      ->will($this->returnValue('id'));
     $this->fieldDefinitions['id']->expects($this->once())
       ->method('getColumns')
       ->will($this->returnValue($columns));
@@ -305,35 +308,47 @@ public function testGetSchema() {
         array('id' => 'id'),
       )));
 
-    $this->entityManager->expects($this->once())
-      ->method('getFieldStorageDefinitions')
-      ->with($this->entityType->id())
-      ->will($this->returnValue($this->fieldDefinitions));
-
     $this->setUpEntityStorage();
 
     $expected = array(
-      'entity_test' => array(
-        'description' => 'The base table for entity_test entities.',
-        'fields' => array(
-          'id' => array(
-            'type' => 'serial',
-            'description' => NULL,
-            'not null' => TRUE,
-          ),
+      'description' => 'The base table for entity_test entities.',
+      'fields' => array(
+        'id' => array(
+          'type' => 'serial',
+          'description' => NULL,
+          'not null' => TRUE,
         ),
-        'primary key' => array('id'),
-        'unique keys' => array(),
-        'indexes' => array(),
-        'foreign keys' => array(),
       ),
+      'primary key' => array('id'),
+      'unique keys' => array(),
+      'indexes' => array(),
+      'foreign keys' => array(),
     );
-    $this->assertEquals($expected, $this->entityStorage->getSchema());
 
-    // Test that repeated calls do not result in repeatedly instantiating
-    // SqlContentEntityStorageSchema as getFieldStorageDefinitions() is only
-    // expected to be called once.
-    $this->assertEquals($expected, $this->entityStorage->getSchema());
+    $schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $schema_handler->expects($this->any())
+      ->method('createTable')
+      ->with($this->equalTo('entity_test'), $this->equalTo($expected));
+
+    $this->connection->expects($this->once())
+      ->method('schema')
+      ->will($this->returnValue($schema_handler));
+
+    $storage = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityDatabaseStorage')
+      ->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache))
+      ->setMethods(array('schemaHandler'))
+      ->getMock();
+
+    $schema_handler = new SqlContentEntityStorageSchema($this->entityManager, $this->entityType, $storage, $this->connection);
+
+    $storage
+      ->expects($this->any())
+      ->method('schemaHandler')
+      ->will($this->returnValue($schema_handler));
+
+    $storage->onEntityTypeCreate($this->entityType);
   }
 
   /**
@@ -397,18 +412,7 @@ public function testGetTableMappingSimple(array $entity_keys) {
   public function testGetTableMappingSimpleWithFields(array $entity_keys) {
     $base_field_names = array('title', 'description', 'owner');
     $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
-
-    $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $this->fieldDefinitions = array_fill_keys($field_names, $definition);
-
-    $this->entityType->expects($this->any())
-      ->method('getKey')
-      ->will($this->returnValueMap(array(
-        array('id', $entity_keys['id']),
-        array('uuid', $entity_keys['uuid']),
-        array('bundle', $entity_keys['bundle']),
-      )));
-
+    $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
     $this->setUpEntityStorage();
 
     $mapping = $this->entityStorage->getTableMapping();
@@ -533,25 +537,11 @@ public function testGetTableMappingRevisionableWithFields(array $entity_keys) {
 
       $base_field_names = array('title');
       $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
-
-      $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-      $this->fieldDefinitions = array_fill_keys($field_names, $definition);
+      $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
 
       $revisionable_field_names = array('description', 'owner');
-      $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-      // isRevisionable() is only called once, but we re-use the same definition
-      // for all revisionable fields.
-      $definition->expects($this->any())
-        ->method('isRevisionable')
-        ->will($this->returnValue(TRUE));
-      $field_names = array_merge(
-        $field_names,
-        $revisionable_field_names
-      );
-      $this->fieldDefinitions += array_fill_keys(
-        array_merge($revisionable_field_names, $revision_metadata_field_names),
-        $definition
-      );
+      $field_names = array_merge($field_names, $revisionable_field_names);
+      $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, $revision_metadata_field_names), array('isRevisionable' => TRUE));
 
       $this->entityType->expects($this->exactly(2))
         ->method('isRevisionable')
@@ -658,9 +648,7 @@ public function testGetTableMappingTranslatableWithFields(array $entity_keys) {
 
     $base_field_names = array('title', 'description', 'owner');
     $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
-
-    $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-    $this->fieldDefinitions = array_fill_keys($field_names, $definition);
+    $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
 
     $this->entityType->expects($this->exactly(2))
       ->method('isTranslatable')
@@ -840,21 +828,10 @@ public function testGetTableMappingRevisionableTranslatableWithFields(array $ent
 
       $base_field_names = array('title');
       $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
-
-      $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-      $this->fieldDefinitions = array_fill_keys($field_names, $definition);
+      $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
 
       $revisionable_field_names = array('description', 'owner');
-      $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
-      // isRevisionable() is only called once, but we re-use the same definition
-      // for all revisionable fields.
-      $definition->expects($this->any())
-        ->method('isRevisionable')
-        ->will($this->returnValue(TRUE));
-      $this->fieldDefinitions += array_fill_keys(
-        array_merge($revisionable_field_names, $revision_metadata_field_names),
-        $definition
-      );
+      $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, $revision_metadata_field_names), array('isRevisionable' => TRUE));
 
       $this->entityType->expects($this->exactly(2))
         ->method('isRevisionable')
@@ -1078,9 +1055,56 @@ public function testCreate() {
   }
 
   /**
+   * Returns a set of mock field definitions for the given names.
+   *
+   * @param array $field_names
+   *   An array of field names.
+   * @param array $methods
+   *   (optional) An associative array of mock method return values keyed by
+   *   method name.
+   *
+   * @return \Drupal\Core\Field\FieldDefinition[]|\PHPUnit_Framework_MockObject_MockObject[]
+   *   An array of mock field definitions.
+   */
+  protected function mockFieldDefinitions(array $field_names, $methods = array()) {
+    $field_definitions = array();
+    $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
+
+    // Assign common method return values.
+    foreach ($methods as $method => $result) {
+      $definition
+        ->expects($this->any())
+        ->method($method)
+        ->will($this->returnValue($result));
+    }
+
+    // Assign field names to mock definitions.
+    foreach ($field_names as $field_name) {
+      $field_definitions[$field_name] = clone $definition;
+      $field_definitions[$field_name]
+        ->expects($this->any())
+        ->method('getName')
+        ->will($this->returnValue($field_name));
+    }
+
+    return $field_definitions;
+  }
+
+  /**
    * Sets up the content entity database storage.
    */
   protected function setUpEntityStorage() {
+    $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->entityManager->expects($this->any())
+      ->method('getDefinition')
+      ->will($this->returnValue($this->entityType));
+
+    $this->entityManager->expects($this->any())
+      ->method('getFieldStorageDefinitions')
+      ->will($this->returnValue($this->fieldDefinitions));
 
     $this->entityManager->expects($this->any())
       ->method('getBaseFieldDefinitions')
@@ -1223,7 +1247,6 @@ public function testLoadMultiplePersistentCacheMiss() {
 
     $entities = $entity_storage->loadMultiple(array($id));
     $this->assertEquals($entity, $entities[$id]);
-
   }
 
   /**
@@ -1239,6 +1262,7 @@ protected function setUpModuleHandlerNoImplementations() {
 
     $this->container->set('module_handler', $this->moduleHandler);
   }
+
 }
 
 /**
diff --git a/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.php
index f1e6a5b..d404b58 100644
--- a/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.php
@@ -82,7 +82,7 @@ protected function setUp() {
    *
    * @covers ::__construct()
    * @covers ::getSchema()
-   * @covers ::getTables()
+   * @covers ::getEntitySchemaTables()
    * @covers ::initializeBaseTable()
    * @covers ::addTableDefaults()
    * @covers ::getEntityIndexName()
@@ -246,16 +246,6 @@ public function testGetSchemaBase() {
       ),
     ));
 
-    $this->setUpSchemaHandler();
-
-    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
-    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
-    $table_mapping->setExtraColumns('entity_test', array('default_langcode'));
-
-    $this->storage->expects($this->once())
-      ->method('getTableMapping')
-      ->will($this->returnValue($table_mapping));
-
     $expected = array(
       'entity_test' => array(
         'description' => 'The base table for entity_test entities.',
@@ -381,9 +371,18 @@ public function testGetSchemaBase() {
         ),
       ),
     );
-    $actual = $this->schemaHandler->getSchema();
 
-    $this->assertEquals($expected, $actual);
+    $this->setUpEntitySchemaHandler($expected);
+
+    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
+    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
+    $table_mapping->setExtraColumns('entity_test', array('default_langcode'));
+
+    $this->storage->expects($this->any())
+      ->method('getTableMapping')
+      ->will($this->returnValue($table_mapping));
+
+    $this->schemaHandler->onEntityTypeCreate($this->entityType);
   }
 
   /**
@@ -391,7 +390,7 @@ public function testGetSchemaBase() {
    *
    * @covers ::__construct()
    * @covers ::getSchema()
-   * @covers ::getTables()
+   * @covers ::getEntitySchemaTables()
    * @covers ::initializeBaseTable()
    * @covers ::initializeRevisionTable()
    * @covers ::addTableDefaults()
@@ -420,16 +419,6 @@ public function testGetSchemaRevisionable() {
       ),
     ));
 
-    $this->setUpSchemaHandler();
-
-    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
-    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
-    $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
-
-    $this->storage->expects($this->once())
-      ->method('getTableMapping')
-      ->will($this->returnValue($table_mapping));
-
     $expected = array(
       'entity_test' => array(
         'description' => 'The base table for entity_test entities.',
@@ -483,9 +472,17 @@ public function testGetSchemaRevisionable() {
       ),
     );
 
-    $actual = $this->schemaHandler->getSchema();
+    $this->setUpEntitySchemaHandler($expected);
+
+    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
+    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
+    $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
+
+    $this->storage->expects($this->any())
+      ->method('getTableMapping')
+      ->will($this->returnValue($table_mapping));
 
-    $this->assertEquals($expected, $actual);
+    $this->schemaHandler->onEntityTypeCreate($this->entityType);
   }
 
   /**
@@ -493,7 +490,7 @@ public function testGetSchemaRevisionable() {
    *
    * @covers ::__construct()
    * @covers ::getSchema()
-   * @covers ::getTables()
+   * @covers ::getEntitySchemaTables()
    * @covers ::initializeDataTable()
    * @covers ::addTableDefaults()
    * @covers ::getEntityIndexName()
@@ -507,7 +504,7 @@ public function testGetSchemaTranslatable() {
       ),
     ));
 
-    $this->storage->expects($this->once())
+    $this->storage->expects($this->any())
       ->method('getDataTable')
       ->will($this->returnValue('entity_test_field_data'));
 
@@ -519,16 +516,6 @@ public function testGetSchemaTranslatable() {
       ),
     ));
 
-    $this->setUpSchemaHandler();
-
-    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
-    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
-    $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
-
-    $this->storage->expects($this->once())
-      ->method('getTableMapping')
-      ->will($this->returnValue($table_mapping));
-
     $expected = array(
       'entity_test' => array(
         'description' => 'The base table for entity_test entities.',
@@ -575,9 +562,17 @@ public function testGetSchemaTranslatable() {
       ),
     );
 
-    $actual = $this->schemaHandler->getSchema();
+    $this->setUpEntitySchemaHandler($expected);
 
-    $this->assertEquals($expected, $actual);
+    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
+    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
+    $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
+
+    $this->storage->expects($this->any())
+      ->method('getTableMapping')
+      ->will($this->returnValue($table_mapping));
+
+    $this->schemaHandler->onEntityTypeCreate($this->entityType);
   }
 
   /**
@@ -585,7 +580,7 @@ public function testGetSchemaTranslatable() {
    *
    * @covers ::__construct()
    * @covers ::getSchema()
-   * @covers ::getTables()
+   * @covers ::getEntitySchemaTables()
    * @covers ::initializeDataTable()
    * @covers ::addTableDefaults()
    * @covers ::getEntityIndexName()
@@ -626,18 +621,6 @@ public function testGetSchemaRevisionableTranslatable() {
       ),
     ));
 
-    $this->setUpSchemaHandler();
-
-    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
-    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
-    $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
-    $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
-    $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions));
-
-    $this->storage->expects($this->once())
-      ->method('getTableMapping')
-      ->will($this->returnValue($table_mapping));
-
     $expected = array(
       'entity_test' => array(
         'description' => 'The base table for entity_test entities.',
@@ -763,26 +746,73 @@ public function testGetSchemaRevisionableTranslatable() {
       ),
     );
 
-    $actual = $this->schemaHandler->getSchema();
+    $this->setUpEntitySchemaHandler($expected);
 
-    $this->assertEquals($expected, $actual);
+    $table_mapping = new DefaultTableMapping($this->storageDefinitions);
+    $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
+    $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
+    $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
+    $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions));
+
+    $this->storage->expects($this->any())
+      ->method('getTableMapping')
+      ->will($this->returnValue($table_mapping));
+
+    $this->schemaHandler->onEntityTypeCreate($this->entityType);
   }
 
   /**
    * Sets up the schema handler.
    *
-   * This uses the field definitions set in $this->fieldDefinitions.
+   * This uses the field definitions set in $this->storageDefinitions.
+   *
+   * @param array $expected
+   *   (optional) An associative array describing the expected entity schema to
+   *   be created. Defaults to expecting nothing.
    */
-  protected function setUpSchemaHandler() {
-    $this->entityManager->expects($this->once())
+  protected function setUpEntitySchemaHandler(array $expected = array()) {
+    $this->entityManager->expects($this->any())
+      ->method('getDefinition')
+      ->with($this->entityType->id())
+      ->will($this->returnValue($this->entityType));
+
+    $this->entityManager->expects($this->any())
       ->method('getFieldStorageDefinitions')
       ->with($this->entityType->id())
       ->will($this->returnValue($this->storageDefinitions));
-    $this->schemaHandler = new SqlContentEntityStorageSchema(
-      $this->entityManager,
-      $this->entityType,
-      $this->storage
-    );
+
+    $db_schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    if ($expected) {
+      $invocation_count = 0;
+      $expected_table_names = array_keys($expected);
+      $expected_table_schemas = array_values($expected);
+
+      $db_schema_handler->expects($this->any())
+        ->method('createTable')
+        ->with(
+          $this->callback(function($table_name) use (&$invocation_count, $expected_table_names) {
+            return $expected_table_names[$invocation_count] == $table_name;
+          }),
+          $this->callback(function($table_schema) use (&$invocation_count, $expected_table_schemas) {
+            return $expected_table_schemas[$invocation_count] == $table_schema;
+          })
+        )
+        ->will($this->returnCallback(function() use (&$invocation_count) {
+          $invocation_count++;
+        }));
+    }
+
+    $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $connection->expects($this->any())
+      ->method('schema')
+      ->will($this->returnValue($db_schema_handler));
+
+    $this->schemaHandler = new SqlContentEntityStorageSchema($this->entityManager, $this->entityType, $this->storage, $connection);
   }
 
   /**
@@ -795,7 +825,11 @@ protected function setUpSchemaHandler() {
    *   FieldStorageDefinitionInterface::getSchema().
    */
   public function setUpStorageDefinition($field_name, array $schema) {
-    $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface');
+    $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
+    // getDescription() is called once for each table.
+    $this->storageDefinitions[$field_name]->expects($this->any())
+      ->method('getName')
+      ->will($this->returnValue($field_name));
     // getDescription() is called once for each table.
     $this->storageDefinitions[$field_name]->expects($this->any())
       ->method('getDescription')
@@ -804,7 +838,7 @@ public function setUpStorageDefinition($field_name, array $schema) {
     $this->storageDefinitions[$field_name]->expects($this->any())
       ->method('getSchema')
       ->will($this->returnValue($schema));
-    $this->storageDefinitions[$field_name]->expects($this->once())
+    $this->storageDefinitions[$field_name]->expects($this->any())
       ->method('getColumns')
       ->will($this->returnValue($schema['columns']));
   }
diff --git a/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php b/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php
new file mode 100644
index 0000000..f685edd
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface.
+ */
+
+namespace Drupal\Tests\Core\Field;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Defines a test interface to mock entity base field definitions.
+ */
+interface TestBaseFieldDefinitionInterface extends FieldDefinitionInterface, FieldStorageDefinitionInterface {
+}
