diff --git a/core/core.services.yml b/core/core.services.yml
index ac24e17..b6cb989 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -320,6 +320,9 @@ services:
   entity.query.sql:
     class: Drupal\Core\Entity\Query\Sql\QueryFactory
     arguments: ['@database']
+  entity.query.keyvalue:
+    class: Drupal\Core\Entity\KeyValueStore\Query\QueryFactory
+    arguments: ['@keyvalue']
   router.dumper:
     class: Drupal\Core\Routing\MatcherDumper
     arguments: ['@database']
diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php
index 5cc1a95..7501565 100644
--- a/core/lib/Drupal/Core/Config/ConfigImporter.php
+++ b/core/lib/Drupal/Core/Config/ConfigImporter.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Config;
 
 use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\Config\Entity\ConfigStorageControllerInterface;
 use Drupal\Core\DependencyInjection\DependencySerialization;
 use Drupal\Core\Lock\LockBackendInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -315,7 +316,8 @@ protected function importInvokeOwner($op, $name) {
       }
 
       $method = 'import' . ucfirst($op);
-      $handled_by_module = $this->configManager->getEntityManager()->getStorageController($entity_type)->$method($name, $new_config, $old_config);
+      $entity_storage = $this->configManager->getEntityManager()->getStorageController($entity_type);
+      $handled_by_module = $entity_storage instanceof ConfigStorageControllerInterface ? $entity_storage->$method($name, $new_config, $old_config) : FALSE;
     }
     if (!empty($handled_by_module)) {
       $this->setProcessed($op, $name);
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
index 18e0260..a370552 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
@@ -230,7 +230,7 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
       ->condition('uuid', $this->uuid())
       ->execute();
     $matched_entity = reset($matching_entities);
-    if (!empty($matched_entity) && ($matched_entity != $this->id())) {
+    if (!empty($matched_entity) && ($matched_entity != $this->id()) && $matched_entity != $this->getOriginalId()) {
       throw new ConfigDuplicateUUIDException(format_string('Attempt to save a configuration entity %id with UUID %uuid when this UUID is already used for %matched', array('%id' => $this->id(), '%uuid' => $this->uuid(), '%matched' => $matched_entity)));
     }
 
diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Query.php b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
index 7332680..612bb1d 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Query.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
@@ -9,15 +9,14 @@
 
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\StorageInterface;
-use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\Query\QueryBase;
+use Drupal\Core\Entity\Query\ArrayQueryBase;
 use Drupal\Core\Entity\Query\QueryInterface;
 
 /**
  * Defines the entity query for configuration entities.
  */
-class Query extends QueryBase implements QueryInterface {
+class Query extends ArrayQueryBase implements QueryInterface {
 
   /**
    * The config storage used by the config entity query.
@@ -74,43 +73,6 @@ public function condition($property, $value = NULL, $operator = NULL, $langcode
   }
 
   /**
-   * Implements \Drupal\Core\Entity\Query\QueryInterface::execute().
-   */
-  public function execute() {
-    // Load the relevant config records.
-    $configs = $this->loadRecords();
-
-    // Apply conditions.
-    $result = $this->condition->compile($configs);
-
-    // Apply sort settings.
-    foreach ($this->sort as $sort) {
-      $direction = $sort['direction'] == 'ASC' ? -1 : 1;
-      $field = $sort['field'];
-      uasort($result, function($a, $b) use ($field, $direction) {
-        return ($a[$field] <= $b[$field]) ? $direction : -$direction;
-      });
-    }
-
-    // Let the pager do its work.
-    $this->initializePager();
-
-    if ($this->range) {
-      $result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
-    }
-    if ($this->count) {
-      return count($result);
-    }
-
-    // Create the expected structure of entity_id => entity_id. Config
-    // entities have string entity IDs.
-    foreach ($result as $key => &$value) {
-      $value = (string) $key;
-    }
-    return $result;
-  }
-
-  /**
    * Loads the config records to examine for the query.
    *
    * @return array
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
new file mode 100644
index 0000000..a95aaef
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
@@ -0,0 +1,286 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore;
+
+use Drupal\Component\Utility\String;
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityMalformedException;
+use Drupal\Core\Entity\EntityStorageControllerBase;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
+
+/**
+ * Provides a key value backend for entities.
+ */
+class KeyValueEntityStorage extends EntityStorageControllerBase {
+
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $keyValue;
+
+  /**
+   * The normalizer for an entity.
+   *
+   * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface
+   */
+  protected $normalizer;
+
+  /**
+   * The UUID service.
+   *
+   * @var \Drupal\Component\Uuid\UuidInterface
+   */
+  protected $uuidService;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new KeyValueEntityStorage.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value
+   *   The key value store.
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
+   *   The UUID service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Symfony\Component\Serializer\Normalizer\NormalizerInterface $normalizer
+   *   (optional) A normalizer for an entity.
+   */
+  public function __construct(EntityTypeInterface $entity_type, KeyValueStoreInterface $key_value, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, NormalizerInterface $normalizer = NULL) {
+    parent::__construct($entity_type);
+    $this->keyValue = $key_value;
+    $this->uuidService = $uuid_service;
+    $this->languageManager = $language_manager;
+    $this->normalizer = $normalizer;
+
+    // Check if the entity type supports UUIDs.
+    $this->uuidKey = $this->entityType->getKey('uuid');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('keyvalue')->get('entity_storage__' . $entity_type->id()),
+      $container->get('uuid'),
+      $container->get('language_manager'),
+      $container->get('serializer',  ContainerInterface::NULL_ON_INVALID_REFERENCE)
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function create(array $values = array()) {
+    $entity_class = $this->entityType->getClass();
+    $entity_class::preCreate($this, $values);
+
+    // Set default language to site default if not provided.
+    $values += array('langcode' => $this->languageManager->getDefaultLanguage()->id);
+
+    $entity = new $entity_class($values, $this->entityTypeId);
+
+    // @todo This is handled by FieldableEntityStorageControllerBase, which
+    //   assumes ContentEntityInterface, which is awful :(
+    if ($entity instanceof ContentEntityInterface) {
+      foreach ($entity as $name => $field) {
+        if (isset($values[$name])) {
+          $entity->$name = $values[$name];
+        }
+        elseif (!array_key_exists($name, $values)) {
+          $entity->get($name)->applyDefaultValue();
+        }
+        unset($values[$name]);
+      }
+    }
+
+    $entity->enforceIsNew();
+
+    // Assign a new UUID if there is none yet.
+    if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
+      $entity->{$this->uuidKey} = $this->uuidService->generate();
+    }
+
+    $entity->postCreate($this);
+
+    // Modules might need to add or change the data initially held by the new
+    // entity object, for instance to fill-in default values.
+    $this->invokeHook('create', $entity);
+
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $ids = NULL) {
+    if (empty($ids)) {
+      $results = $this->keyValue->getAll();
+    }
+    else {
+      $results = $this->keyValue->getMultiple($ids);
+    }
+    $class = $this->entityType->getClass();
+    $entities = array();
+    foreach ($results as $result) {
+      $entity = new $class($result, $this->entityTypeId);
+      $entities[$entity->id()] = $entity;
+    }
+    return $entities;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $entities = $this->loadMultiple(array($id));
+    return isset($entities[$id]) ? $entities[$id] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadRevision($revision_id) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteRevision($revision_id) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete(array $entities) {
+    if (!$entities) {
+      // If no IDs or invalid IDs were passed, do nothing.
+      return;
+    }
+
+    $entity_class = $this->entityType->getClass();
+    $entity_class::preDelete($this, $entities);
+    foreach ($entities as $entity) {
+      $this->invokeHook('predelete', $entity);
+    }
+
+    $entity_ids = array();
+    foreach ($entities as $entity) {
+      $entity_ids[] = $entity->id();
+    }
+    $this->keyValue->deleteMultiple($entity_ids);
+
+    $entity_class::postDelete($this, $entities);
+    foreach ($entities as $entity) {
+      $this->invokeHook('delete', $entity);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(EntityInterface $entity) {
+    $id = $entity->id();
+    if ($id === NULL || $id === '') {
+      throw new EntityMalformedException('The entity does not have an ID.');
+    }
+
+    // If this entity has a concept of 'original ID', use that.
+    if ($entity instanceof ConfigEntityInterface && $entity->getOriginalId() !== NULL) {
+      $id = $entity->getOriginalId();
+    }
+
+    // Track if this entity is new.
+    $is_new = $entity->isNew();
+    // Track if this entity exists already.
+    $id_exists = $this->keyValue->has($id);
+
+    // A new entity should not already exist.
+    if ($id_exists && $is_new) {
+      throw new EntityStorageException(String::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
+    }
+
+    // Load the original entity, if any.
+    if ($id_exists && !isset($entity->original)) {
+      $entity->original = $this->loadUnchanged($id);
+    }
+
+    // If this is a rename, delete the original entity.
+    if ($id_exists && $id !== $entity->id()) {
+      $this->keyValue->delete($entity->original->id());
+    }
+
+    // Allow code to run before saving.
+    $entity->preSave($this);
+    $this->invokeHook('presave', $entity);
+
+    // Prefer the entity normalizer.
+    // @todo Always use normalization after https://drupal.org/node/2216569.
+    if ($this->normalizer) {
+      $data = $this->normalizer->normalize($entity);
+    }
+    elseif ($entity instanceof ConfigEntityInterface) {
+      $data = $entity->getExportProperties();
+    }
+    elseif ($entity instanceof ContentEntityInterface) {
+      $data = $entity->getPropertyValues();
+    }
+    else {
+      throw new EntityStorageException(String::format('Cannot normalize entity of type @entity_type, enable the serialization module.', array('@entity_type' => $this->getEntityTypeId())));
+    }
+
+    // Save the entity data in the key value store.
+    $this->keyValue->set($entity->id(), $data);
+
+    // The entity is no longer new.
+    $entity->enforceIsNew(FALSE);
+
+    // Allow code to run after saving.
+    $entity->postSave($this, !$is_new);
+    $this->invokeHook($is_new ? 'insert' : 'update', $entity);
+
+    // If this entity has a concept of 'original ID', update it.
+    if ($id_exists && $entity instanceof ConfigEntityInterface) {
+      $entity->setOriginalId($entity->id());
+    }
+
+    unset($entity->original);
+
+    return $is_new ? SAVED_NEW : SAVED_UPDATED;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQueryServicename() {
+    return 'entity.query.keyvalue';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
new file mode 100644
index 0000000..024511b
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Condition.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\Query\Condition.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore\Query;
+
+use Drupal\Core\Config\Entity\Query\Condition as ConditionParent;
+
+/**
+ * Defines the condition class for the key value entity query.
+ */
+class Condition extends ConditionParent {
+
+}
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php
new file mode 100644
index 0000000..4fafbe9
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/Query.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\Query\Query.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore\Query;
+
+use Drupal\Core\Config\Entity\Query\Query as QueryParent;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\ArrayQueryBase;
+use Drupal\Core\Entity\Query\QueryAggregateInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Defines the entity query for entities stored in a key value backend.
+ */
+class Query extends ArrayQueryBase {
+
+  /**
+   * The key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
+   */
+  protected $keyValueFactory;
+
+  /**
+   * Constructs a new Query.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   * @param string $conjunction
+   *   - AND: all of the conditions on the query need to match.
+   *   - OR: at least one of the conditions on the query need to match.
+   * @param array $namespaces
+   *   List of potential namespaces of the classes belonging to this query.
+   * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
+   *   The key value factory.
+   */
+  public function __construct(EntityTypeInterface $entity_type, $conjunction, array $namespaces, KeyValueFactoryInterface $key_value_factory) {
+    parent::__construct($entity_type, $conjunction, $namespaces);
+    $this->keyValueFactory = $key_value_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function loadRecords() {
+    return $this->keyValueFactory->get('entity_storage__' . $this->entityTypeId)->getAll();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php
new file mode 100644
index 0000000..47de2c5
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/Query/QueryFactory.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\Query\QueryFactory.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore\Query;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryException;
+use Drupal\Core\Entity\Query\QueryFactoryInterface;
+use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
+
+/**
+ * Provides a factory for creating the key value entity query.
+ */
+class QueryFactory implements QueryFactoryInterface {
+
+  /**
+   * The key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
+   */
+  protected $keyValueFactory;
+
+  /**
+   * The namespace of this class, the parent class etc.
+   *
+   * @var array
+   */
+  protected $namespaces;
+
+  /**
+   * Constructs a QueryFactory object.
+   *
+   */
+  public function __construct(KeyValueFactoryInterface $key_value_factory) {
+    $this->keyValueFactory = $key_value_factory;
+    $this->namespaces = Query::getNamespaces($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get(EntityTypeInterface $entity_type, $conjunction) {
+    return new Query($entity_type, $conjunction, $this->namespaces, $this->keyValueFactory);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
+    throw new QueryException('Aggregation over key-value entity storage is not supported');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Query/ArrayQueryBase.php b/core/lib/Drupal/Core/Entity/Query/ArrayQueryBase.php
new file mode 100644
index 0000000..72d8504
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Query/ArrayQueryBase.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Query\ArrayQueryBase.
+ */
+
+namespace Drupal\Core\Entity\Query;
+
+/**
+ * Provides a base class for entity query that acts upon array-based results.
+ */
+abstract class ArrayQueryBase extends QueryBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    // Load the relevant config records.
+    $configs = $this->loadRecords();
+
+    // Apply conditions.
+    $result = $this->condition->compile($configs);
+
+    // Apply sort settings.
+    foreach ($this->sort as $sort) {
+      $direction = $sort['direction'] == 'ASC' ? -1 : 1;
+      $field = $sort['field'];
+      uasort($result, function($a, $b) use ($field, $direction) {
+        return ($a[$field] <= $b[$field]) ? $direction : -$direction;
+      });
+    }
+
+    // Let the pager do its work.
+    $this->initializePager();
+
+    if ($this->range) {
+      $result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
+    }
+    if ($this->count) {
+      return count($result);
+    }
+
+    // Create the expected structure of entity_id => entity_id. Config
+    // entities have string entity IDs.
+    foreach ($result as $key => &$value) {
+      $value = (string) $key;
+    }
+    return $result;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
index b0791e2..b2f97dc 100644
--- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorage.php
@@ -47,6 +47,16 @@ public function __construct($collection, Connection $connection, $table = 'key_v
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function has($key) {
+    return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name = :key', array(
+      ':collection' => $this->collection,
+      ':key' => $key,
+    ))->fetchField();
+  }
+
+  /**
    * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getMultiple().
    */
   public function getMultiple(array $keys) {
diff --git a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
index 91e7752..fb0b8b0 100644
--- a/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
+++ b/core/lib/Drupal/Core/KeyValueStore/DatabaseStorageExpirable.php
@@ -57,6 +57,17 @@ public function __construct($collection, Connection $connection, $table = 'key_v
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function has($key) {
+    return (bool) $this->connection->query('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name = :key AND expire > :now', array(
+      ':collection' => $this->collection,
+      ':key' => $key,
+      ':now' => REQUEST_TIME,
+    ))->fetchField();
+  }
+
+  /**
    * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::getMultiple().
    */
   public function getMultiple(array $keys) {
diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
index 60c150c..39d1da0 100644
--- a/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
+++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueStoreInterface.php
@@ -21,6 +21,17 @@
   public function getCollectionName();
 
   /**
+   * Returns whether a given key exists in the store.
+   *
+   * @param string $key
+   *   The key to check.
+   *
+   * @return bool
+   *   TRUE if the key exists, FALSE otherwise.
+   */
+  public function has($key);
+
+  /**
    * Returns the stored value for a given key.
    *
    * @param string $key
diff --git a/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php b/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
index e6e07ef..6e9b0f7 100644
--- a/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
+++ b/core/lib/Drupal/Core/KeyValueStore/MemoryStorage.php
@@ -20,6 +20,13 @@ class MemoryStorage extends StorageBase {
   protected $data = array();
 
   /**
+   * {@inheritdoc}
+   */
+  public function has($key) {
+    return array_key_exists($key, $this->data);
+  }
+
+  /**
    * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::get().
    */
   public function get($key, $default = NULL) {
diff --git a/core/lib/Drupal/Core/KeyValueStore/NullStorageExpirable.php b/core/lib/Drupal/Core/KeyValueStore/NullStorageExpirable.php
index 29bcde6..a3704eb 100644
--- a/core/lib/Drupal/Core/KeyValueStore/NullStorageExpirable.php
+++ b/core/lib/Drupal/Core/KeyValueStore/NullStorageExpirable.php
@@ -34,6 +34,13 @@ public function __construct($collection) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function has($key) {
+    return FALSE;
+  }
+
+  /**
    * Implements Drupal\Core\KeyValueStore\KeyValueStoreInterface::get().
    */
   public function get($key, $default = NULL) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php
index 19a261b..5f7365d 100644
--- a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/DatabaseStorageExpirableTest.php
@@ -141,7 +141,9 @@ public function testExpiration() {
     $stores[0]->set('troubles', 'here to stay');
 
     // Only the non-expired item should be returned.
+    $this->assertFalse($stores[0]->has('yesterday'));
     $this->assertFalse($stores[0]->get('yesterday'));
+    $this->assertTrue($stores[0]->has('troubles'));
     $this->assertIdentical($stores[0]->get('troubles'), 'here to stay');
     $this->assertIdentical(count($stores[0]->getMultiple(array('yesterday', 'troubles'))), 1);
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueConfigEntityStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueConfigEntityStorageTest.php
new file mode 100644
index 0000000..a814c50
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueConfigEntityStorageTest.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\KeyValueStore\KeyValueConfigEntityStorageTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+use Drupal\config\Tests\ConfigEntityTest;
+
+/**
+ * Tests config entity CRUD with key value entity storage.
+ */
+class KeyValueConfigEntityStorageTest extends ConfigEntityTest {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('keyvalue_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'KeyValueEntityStorage config entity test',
+      'description' => 'Tests KeyValueEntityStorage for config entities.',
+      'group' => 'Entity API',
+    );
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueContentEntityStorageTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueContentEntityStorageTest.php
new file mode 100644
index 0000000..726deac
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/KeyValueContentEntityStorageTest.php
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\KeyValueStore\KeyValueContentEntityStorageTest.
+ */
+
+namespace Drupal\system\Tests\KeyValueStore;
+
+use Drupal\Core\Entity\EntityMalformedException;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests content entity CRUD with key value entity storage.
+ */
+class KeyValueContentEntityStorageTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('keyvalue_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'KeyValueEntityStorage content entity test',
+      'description' => 'Tests KeyValueEntityStorage for content entities.',
+      'group' => 'Entity API',
+    );
+  }
+
+  /**
+   * Tests CRUD operations.
+   */
+  function testCRUD() {
+    $default_langcode = language_default()->id;
+    // Verify default properties on a newly created empty entity.
+    $empty = entity_create('entity_test_label');
+    $this->assertIdentical($empty->id->value, NULL);
+    $this->assertIdentical($empty->name->value, NULL);
+    $this->assertTrue($empty->uuid->value);
+    $this->assertIdentical($empty->langcode->value, $default_langcode);
+
+    // Verify ConfigEntity properties/methods on the newly created empty entity.
+    $this->assertIdentical($empty->isNew(), TRUE);
+    $this->assertIdentical($empty->bundle(), 'entity_test_label');
+    $this->assertIdentical($empty->id(), NULL);
+    $this->assertTrue($empty->uuid());
+    $this->assertIdentical($empty->label(), NULL);
+
+    // Verify Entity properties/methods on the newly created empty entity.
+    $this->assertIdentical($empty->getEntityTypeId(), 'entity_test_label');
+    // The URI can only be checked after saving.
+    try {
+      $empty->urlInfo();
+      $this->fail('EntityMalformedException was thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->pass('EntityMalformedException was thrown.');
+    }
+
+    // Verify that an empty entity cannot be saved.
+    try {
+      $empty->save();
+      $this->fail('EntityMalformedException was thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->pass('EntityMalformedException was thrown.');
+    }
+
+    // Verify that an entity with an empty ID string is considered empty, too.
+    $empty_id = entity_create('entity_test_label', array(
+      'id' => '',
+    ));
+    $this->assertIdentical($empty_id->isNew(), TRUE);
+    try {
+      $empty_id->save();
+      $this->fail('EntityMalformedException was thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->pass('EntityMalformedException was thrown.');
+    }
+
+    // Verify properties on a newly created entity.
+    $entity_test = entity_create('entity_test_label', $expected = array(
+      'id' => $this->randomName(),
+      'name' => $this->randomString(),
+    ));
+    $this->assertIdentical($entity_test->id->value, $expected['id']);
+    $this->assertTrue($entity_test->uuid->value);
+    $this->assertNotEqual($entity_test->uuid->value, $empty->uuid->value);
+    $this->assertIdentical($entity_test->name->value, $expected['name']);
+    $this->assertIdentical($entity_test->langcode->value, $default_langcode);
+
+    // Verify methods on the newly created entity.
+    $this->assertIdentical($entity_test->isNew(), TRUE);
+    $this->assertIdentical($entity_test->id(), $expected['id']);
+    $this->assertTrue($entity_test->uuid());
+    $expected['uuid'] = $entity_test->uuid();
+    $this->assertIdentical($entity_test->label(), $expected['name']);
+
+    // Verify that the entity can be saved.
+    try {
+      $status = $entity_test->save();
+      $this->pass('EntityMalformedException was not thrown.');
+    }
+    catch (EntityMalformedException $e) {
+      $this->fail('EntityMalformedException was not thrown.');
+    }
+
+    // Verify that the correct status is returned and properties did not change.
+    $this->assertIdentical($status, SAVED_NEW);
+    $this->assertIdentical($entity_test->id(), $expected['id']);
+    $this->assertIdentical($entity_test->uuid(), $expected['uuid']);
+    $this->assertIdentical($entity_test->label(), $expected['name']);
+    $this->assertIdentical($entity_test->isNew(), FALSE);
+
+    // Save again, and verify correct status and properties again.
+    $status = $entity_test->save();
+    $this->assertIdentical($status, SAVED_UPDATED);
+    $this->assertIdentical($entity_test->id(), $expected['id']);
+    $this->assertIdentical($entity_test->uuid(), $expected['uuid']);
+    $this->assertIdentical($entity_test->label(), $expected['name']);
+    $this->assertIdentical($entity_test->isNew(), FALSE);
+
+    // Ensure that creating an entity with the same id as an existing one is not
+    // possible.
+    $same_id = entity_create('entity_test_label', array(
+      'id' => $entity_test->id(),
+    ));
+    $this->assertIdentical($same_id->isNew(), TRUE);
+    try {
+      $same_id->save();
+      $this->fail('Not possible to overwrite an entity entity.');
+    } catch (EntityStorageException $e) {
+      $this->pass('Not possible to overwrite an entity entity.');
+    }
+
+    // Verify that renaming the ID returns correct status and properties.
+    $ids = array($expected['id'], 'second_' . $this->randomName(4), 'third_' . $this->randomName(4));
+    for ($i = 1; $i < 3; $i++) {
+      $old_id = $ids[$i - 1];
+      $new_id = $ids[$i];
+      // Before renaming, everything should point to the current ID.
+      $this->assertIdentical($entity_test->id(), $old_id);
+
+      // Rename.
+      $entity_test->id = $new_id;
+      $this->assertIdentical($entity_test->id(), $new_id);
+      $status = $entity_test->save();
+      $this->assertIdentical($status, SAVED_UPDATED);
+      $this->assertIdentical($entity_test->isNew(), FALSE);
+
+      // Verify that originalID points to new ID directly after renaming.
+      $this->assertIdentical($entity_test->id(), $new_id);
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
index 86e6ce8..f8c8de9 100644
--- a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/StorageTestBase.php
@@ -83,8 +83,10 @@ public function testCRUD() {
 
     // Verify that an item can be stored.
     $stores[0]->set('foo', $this->objects[0]);
+    $this->assertTrue($stores[0]->has('foo'));
     $this->assertIdenticalObject($this->objects[0], $stores[0]->get('foo'));
     // Verify that the other collection is not affected.
+    $this->assertFalse($stores[1]->has('foo'));
     $this->assertFalse($stores[1]->get('foo'));
 
     // Verify that an item can be updated.
@@ -100,9 +102,11 @@ public function testCRUD() {
 
     // Verify that an item can be deleted.
     $stores[0]->delete('foo');
+    $this->assertFalse($stores[0]->has('foo'));
     $this->assertFalse($stores[0]->get('foo'));
 
     // Verify that the other collection is not affected.
+    $this->assertTrue($stores[1]->has('foo'));
     $this->assertIdenticalObject($this->objects[2], $stores[1]->get('foo'));
     $stores[1]->delete('foo');
     $this->assertFalse($stores[1]->get('foo'));
diff --git a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml
new file mode 100644
index 0000000..41cfb33
--- /dev/null
+++ b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.info.yml
@@ -0,0 +1,10 @@
+name: 'KeyValue tests'
+type: module
+description: 'A support module to test key value storage.'
+core: 8.x
+package: Testing
+version: VERSION
+hidden: true
+dependencies:
+  - config_test
+  - entity_test
diff --git a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
new file mode 100644
index 0000000..96404a1
--- /dev/null
+++ b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Sets up the key value entity storage.
+ */
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function keyvalue_test_entity_type_alter(array &$entity_types) {
+  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  if (isset($entity_types['config_test'])) {
+    $entity_types['config_test']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
+  }
+  if (isset($entity_types['entity_test_label'])) {
+    $entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
new file mode 100644
index 0000000..e86b7f2
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
@@ -0,0 +1,435 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\KeyValueStore\KeyValueEntityStorageTest.
+ */
+
+namespace Drupal\Tests\Core\Entity\KeyValueStore {
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
+ *
+ * @group Drupal
+ * @group KeyValueEntityStorage
+ */
+class KeyValueEntityStorageTest extends UnitTestCase {
+
+  /**
+   * The entity type.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityType;
+
+  /**
+   * The key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $keyValueStore;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The UUID service.
+   *
+   * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $uuidService;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * The normalizer for an entity.
+   *
+   * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $normalizer;
+
+  /**
+   * @var \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
+   */
+  protected $entityStorage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'KeyValueEntityStorage',
+      'description' => 'Tests \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage',
+      'group' => 'Entity',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $entity = $this->getMockForAbstractClass('Drupal\Core\Entity\Entity', array(), '', FALSE, TRUE, TRUE, array('onSaveOrDelete'));
+    $entity->expects($this->any())
+      ->method('onSaveOrDelete');
+
+    $this->entityType = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $this->entityType->expects($this->any())
+      ->method('getClass')
+      ->will($this->returnValue(get_class($entity)));
+    $this->entityType->expects($this->any())
+      ->method('getKey')
+      ->with('uuid')
+      ->will($this->returnValue('uuid'));
+    $this->entityType->expects($this->any())
+      ->method('id')
+      ->will($this->returnValue('test_entity_type'));
+
+    $this->keyValueStore = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->uuidService = $this->getMock('Drupal\Component\Uuid\UuidInterface');
+    $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $this->languageManager->expects($this->any())
+      ->method('getDefaultLanguage')
+      ->will($this->returnValue(new Language(array('langcode' => 'en'))));
+    $this->normalizer = $this->getMock('Symfony\Component\Serializer\Normalizer\NormalizerInterface');
+
+    $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager, $this->normalizer);
+    $this->entityStorage->setModuleHandler($this->moduleHandler);
+  }
+
+  /**
+   * @covers ::create()
+   */
+  public function testCreateWithPredefinedUuid() {
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_create');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_create');
+    $this->uuidService->expects($this->never())
+      ->method('generate');
+
+    $entity = $this->entityStorage->create(array('id' => 'foo', 'uuid' => 'baz'));
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+    $this->assertSame('baz', $entity->uuid());
+  }
+
+  /**
+   * @covers ::create()
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   */
+  public function testCreate() {
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_create');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_create');
+    $this->uuidService->expects($this->once())
+      ->method('generate')
+      ->will($this->returnValue('bar'));
+
+    $entity = $this->entityStorage->create(array('id' => 'foo'));
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+    $this->assertSame('bar', $entity->uuid());
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *
+   * @depends testCreate
+   */
+  public function testSaveInsert(EntityInterface $entity) {
+    $expected = array('id' => 'foo');
+    $this->normalizer->expects($this->once())
+      ->method('normalize')
+      ->with($entity)
+      ->will($this->returnValue($expected));
+
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_presave');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_presave');
+    $this->moduleHandler->expects($this->at(2))
+      ->method('invokeAll')
+      ->with('test_entity_type_insert');
+    $this->moduleHandler->expects($this->at(3))
+      ->method('invokeAll')
+      ->with('entity_insert');
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $return = $this->entityStorage->save($entity);
+    $this->assertSame(SAVED_NEW, $return);
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *
+   * @depends testSaveInsert
+   */
+  public function testSaveUpdate(EntityInterface $entity) {
+    $expected = array('id' => 'foo');
+    $this->normalizer->expects($this->once())
+      ->method('normalize')
+      ->with($entity)
+      ->will($this->returnValue($expected));
+
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_presave');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_presave');
+    $this->moduleHandler->expects($this->at(2))
+      ->method('invokeAll')
+      ->with('test_entity_type_update');
+    $this->moduleHandler->expects($this->at(3))
+      ->method('invokeAll')
+      ->with('entity_update');
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $return = $this->entityStorage->save($entity);
+    $this->assertSame(SAVED_UPDATED, $return);
+    return $entity;
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @expectedException \Drupal\Core\Entity\EntityMalformedException
+   * @expectedExceptionMessage The entity does not have an ID.
+   */
+  public function testSaveInvalid() {
+    $entity = $this->entityStorage->create(array());
+    $this->entityStorage->save($entity);
+  }
+
+  /**
+   * @covers ::save()
+   *
+   * @expectedException \Drupal\Core\Entity\EntityStorageException
+   * @expectedExceptionMessage Cannot normalize entity of type test_entity_type, enable the serialization module.
+   */
+  public function testSaveWithoutNormalizer() {
+    $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager);
+    $this->entityStorage->setModuleHandler($this->moduleHandler);
+    $entity = $this->entityStorage->create(array('id' => 'foo'));
+    $this->entityStorage->save($entity);
+  }
+
+  /**
+   * @covers ::save()
+   */
+  public function testSaveConfigEntityWithoutNormalizer() {
+    $expected = array('id' => 'foo');
+    $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager);
+    $this->entityStorage->setModuleHandler($this->moduleHandler);
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $entity = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('id')
+      ->will($this->returnValue('foo'));
+    $entity->expects($this->once())
+      ->method('getExportProperties')
+      ->will($this->returnValue($expected));
+    $this->entityStorage->save($entity);
+  }
+
+  /**
+   * @covers ::save()
+   */
+  public function testSaveContentEntityWithoutNormalizer() {
+    $expected = array('id' => 'foo');
+    $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager);
+    $this->entityStorage->setModuleHandler($this->moduleHandler);
+    $this->keyValueStore->expects($this->once())
+      ->method('set')
+      ->with('foo', $expected);
+    $entity = $this->getMock('Drupal\Tests\Core\Entity\KeyValueStore\TestContentEntityInterface');
+    $entity->expects($this->atLeastOnce())
+      ->method('id')
+      ->will($this->returnValue('foo'));
+    $entity->expects($this->once())
+      ->method('getPropertyValues')
+      ->will($this->returnValue($expected));
+    $this->entityStorage->save($entity);
+  }
+
+  /**
+   * @covers ::load()
+   */
+  public function testLoad() {
+    $this->keyValueStore->expects($this->once())
+      ->method('getMultiple')
+      ->with(array('foo'))
+      ->will($this->returnValue(array(array('id' => 'foo'))));
+    $entity = $this->entityStorage->load('foo');
+    $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+    $this->assertSame('foo', $entity->id());
+  }
+
+  /**
+   * @covers ::loadMultiple()
+   */
+  public function testLoadMultipleAll() {
+    $expected[] = $this->getMockEntity();
+    $expected[] = $this->getMockEntity();
+    $this->keyValueStore->expects($this->once())
+      ->method('getAll')
+      ->will($this->returnValue(array(array('id' => 'foo'), array('id' => 'bar'))));
+    $entities = $this->entityStorage->loadMultiple();
+    foreach ($entities as $id => $entity) {
+      $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+      $this->assertSame($id, $entity->id());
+    }
+  }
+
+  /**
+   * @covers ::loadMultiple()
+   */
+  public function testLoadMultipleIds() {
+    $expected[] = $this->getMockEntity();
+    $this->keyValueStore->expects($this->once())
+      ->method('getMultiple')
+      ->with(array('foo'))
+      ->will($this->returnValue(array(array('id' => 'foo'))));
+    $entities = $this->entityStorage->loadMultiple(array('foo'));
+    foreach ($entities as $id => $entity) {
+      $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
+      $this->assertSame($id, $entity->id());
+    }
+  }
+
+  /**
+   * @covers ::loadRevision()
+   */
+  public function testLoadRevision() {
+    $this->assertSame(FALSE, $this->entityStorage->loadRevision(1));
+  }
+
+  /**
+   * @covers ::deleteRevision()
+   */
+  public function testDeleteRevision() {
+    $this->assertSame(NULL, $this->entityStorage->deleteRevision(1));
+  }
+
+  /**
+   * @covers ::delete()
+   */
+  public function testDelete() {
+    $entities = array();
+    foreach (array('foo', 'bar') as $id) {
+      $entity = $this->getMockEntity(array('id'));
+      $entity->expects($this->once())
+        ->method('id')
+        ->will($this->returnValue($id));
+      $entities[] = $entity;
+    }
+    $this->moduleHandler->expects($this->at(0))
+      ->method('invokeAll')
+      ->with('test_entity_type_predelete');
+    $this->moduleHandler->expects($this->at(1))
+      ->method('invokeAll')
+      ->with('entity_predelete');
+    $this->moduleHandler->expects($this->at(2))
+      ->method('invokeAll')
+      ->with('test_entity_type_predelete');
+    $this->moduleHandler->expects($this->at(3))
+      ->method('invokeAll')
+      ->with('entity_predelete');
+    $this->moduleHandler->expects($this->at(4))
+      ->method('invokeAll')
+      ->with('test_entity_type_delete');
+    $this->moduleHandler->expects($this->at(5))
+      ->method('invokeAll')
+      ->with('entity_delete');
+    $this->moduleHandler->expects($this->at(6))
+      ->method('invokeAll')
+      ->with('test_entity_type_delete');
+    $this->moduleHandler->expects($this->at(7))
+      ->method('invokeAll')
+      ->with('entity_delete');
+
+    $this->keyValueStore->expects($this->once())
+      ->method('deleteMultiple')
+      ->with(array('foo', 'bar'));
+    $this->entityStorage->delete($entities);
+  }
+
+  /**
+   * @covers ::delete()
+   */
+  public function testDeleteNothing() {
+    $this->moduleHandler->expects($this->never())
+      ->method($this->anything());
+    $this->keyValueStore->expects($this->never())
+      ->method('deleteMultiple');
+
+    $this->entityStorage->delete(array());
+  }
+
+  protected function getMockEntity($methods = array()) {
+    $methods[] = 'onSaveOrDelete';
+    $entity = $this->getMockForAbstractClass('Drupal\Core\Entity\Entity', array(), '', FALSE, TRUE, TRUE, $methods);
+    $entity->expects($this->any())
+      ->method('onSaveOrDelete');
+    return $entity;
+  }
+
+}
+
+/**
+ * Provides a testable version of ContentEntityInterface.
+ *
+ * @see https://github.com/sebastianbergmann/phpunit-mock-objects/commit/96a6794
+ */
+interface TestContentEntityInterface extends \Iterator, ContentEntityInterface {
+}
+
+}
+namespace {
+  if (!defined('SAVED_NEW')) {
+    define('SAVED_NEW', 1);
+  }
+  if (!defined('SAVED_UPDATED')) {
+    define('SAVED_UPDATED', 2);
+  }
+}
