diff --git a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
index 0fccd47..5127adc 100644
--- a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
+++ b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
@@ -81,7 +81,7 @@ protected function checkValue($key, $value) {
     $error_key = $this->configName . ':' . $key;
     $element = $this->schema->get($key);
     if ($element instanceof Undefined) {
-      return array($error_key => 'Missing schema.');
+      return array($error_key => 'missing schema');
     }
 
     // Do not check value if it is defined to be ignored.
@@ -104,13 +104,13 @@ protected function checkValue($key, $value) {
       }
       $class = get_class($element);
       if (!$success) {
-        return array($error_key => "Variable type is $type but applied schema class is $class.");
+        return array($error_key => "variable type is $type but applied schema class is $class");
       }
     }
     else {
       $errors = array();
       if (!$element instanceof TraversableTypedDataInterface) {
-        $errors[$error_key] = 'Non-scalar value but not defined as an array (such as mapping or sequence).';
+        $errors[$error_key] = 'non-scalar value but not defined as an array (such as mapping or sequence)';
       }
 
       // Go on processing so we can get errors on all levels. Any non-scalar
diff --git a/core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php b/core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php
new file mode 100644
index 0000000..db80944
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Config\Testing\ConfigSchemaChecker.
+ */
+
+namespace Drupal\Core\Config\Testing;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\Config\Schema\SchemaCheckTrait;
+use Drupal\Core\Config\Schema\SchemaIncompleteException;
+use Drupal\Core\Config\TypedConfigManagerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Listens to the config save event and validates schema.
+ *
+ * If tests have the $strictConfigSchema property set to TRUE this event
+ * listener will be added to the container and throw exceptions if configuration
+ * is invalid.
+ *
+ * @see \Drupal\simpletest\WebTestBase::setUp()
+ * @see \Drupal\simpletest\KernelTestBase::containerBuild()
+ */
+class ConfigSchemaChecker implements EventSubscriberInterface {
+  use SchemaCheckTrait;
+
+  /**
+   * The typed config manger.
+   *
+   * @var \Drupal\Core\Config\TypedConfigManagerInterface
+   */
+  protected $typedManager;
+
+  /**
+   * An array of config checked already. Keyed by config name and a checksum.
+   *
+   * @var array
+   */
+  protected $checked = array();
+
+  /**
+   * Constructs the ConfigSchemaChecker object.
+   *
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_manager
+   *   The typed config manager.
+   */
+  public function __construct(TypedConfigManagerInterface $typed_manager) {
+    $this->typedManager = $typed_manager;
+  }
+
+  /**
+   * Checks that configuration complies with its schema on config save.
+   *
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   *   The configuration event.
+   *
+   * @throws \Drupal\Core\Config\Schema\SchemaIncompleteException
+   *   Exception thrown when configuration does not match its schema.
+   */
+  public function onConfigSave(ConfigCrudEvent $event) {
+    $saved_config = $event->getConfig();
+    $name = $saved_config->getName();
+    $data = $saved_config->get();
+    $checksum = crc32(serialize($data));
+    if (!isset($this->checked[$name . ':' . $checksum])) {
+      $this->checked[$name . ':' . $checksum] = TRUE;
+      $errors = $this->checkConfigSchema($this->typedManager, $name, $data);
+      if ($errors === FALSE) {
+        throw new SchemaIncompleteException(String::format('No schema for @config_name', array('@config_name' => $name)));
+      }
+      elseif (is_array($errors)) {
+        $text_errors = [];
+        foreach ($errors as $key => $error) {
+          $text_errors[] = String::format('@key @error', array('@key' => $key, '@error' => $error));
+        }
+        throw new SchemaIncompleteException(String::format('Schema errors for @config_name with the following errors: @errors', array('@config_name' => $name, '@errors' => implode(', ', $text_errors))));
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[ConfigEvents::SAVE][] = array('onConfigSave', 255);
+    return $events;
+  }
+
+}
diff --git a/core/modules/config/src/Tests/SchemaCheckTestTrait.php b/core/modules/config/src/Tests/SchemaCheckTestTrait.php
index c5b4cba..3a88296 100644
--- a/core/modules/config/src/Tests/SchemaCheckTestTrait.php
+++ b/core/modules/config/src/Tests/SchemaCheckTestTrait.php
@@ -45,7 +45,7 @@ public function assertConfigSchema(TypedConfigManagerInterface $typed_config, $c
       foreach ($errors as $key => $error) {
         // @todo Since the use of this trait is under TestBase, it works.
         //  Can be fixed as part of https://drupal.org/node/2260053.
-        $this->fail($key . ': ' . $error);
+        $this->fail(String::format('Schema key @key failed with: @error', array('@key' => $key, '@error' => $error)));
       }
     }
   }
diff --git a/core/modules/config/src/Tests/SchemaCheckTraitTest.php b/core/modules/config/src/Tests/SchemaCheckTraitTest.php
index 890eed8..450d4f1 100644
--- a/core/modules/config/src/Tests/SchemaCheckTraitTest.php
+++ b/core/modules/config/src/Tests/SchemaCheckTraitTest.php
@@ -61,9 +61,9 @@ public function testTrait() {
     $config_data['boolean'] = array();
     $ret = $this->checkConfigSchema($this->typedConfig, 'config_test.types', $config_data);
     $expected = array(
-      'config_test.types:new_key' => 'Missing schema.',
-      'config_test.types:new_array' => 'Missing schema.',
-      'config_test.types:boolean' => 'Non-scalar value but not defined as an array (such as mapping or sequence).',
+      'config_test.types:new_key' => 'missing schema',
+      'config_test.types:new_array' => 'missing schema',
+      'config_test.types:boolean' => 'non-scalar value but not defined as an array (such as mapping or sequence)',
     );
     $this->assertIdentical($ret, $expected);
   }
diff --git a/core/modules/config/src/Tests/SchemaConfigListenerTest.php b/core/modules/config/src/Tests/SchemaConfigListenerTest.php
new file mode 100644
index 0000000..84bf60e
--- /dev/null
+++ b/core/modules/config/src/Tests/SchemaConfigListenerTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Tests\SchemaConfigListenerTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\Schema\SchemaCheckTrait;
+use Drupal\Core\Config\Schema\SchemaIncompleteException;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests the functionality of ConfigSchemaChecker in KernelTestBase tests.
+ *
+ * @group config
+ */
+class SchemaConfigListenerTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('config_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $strictConfigSchema = TRUE;
+
+  /**
+   * Tests \Drupal\Core\Config\Testing\ConfigSchemaChecker.
+   */
+  public function testConfigSchemaChecker() {
+    // Test a non-existing schema.
+    $message = 'Expected SchemaIncompleteException thrown';
+    try {
+      \Drupal::config('config_schema_test.noschema')->set('foo', 'bar')->save();
+      $this->fail($message);
+    }
+    catch (SchemaIncompleteException $e) {
+      $this->pass($message);
+      $this->assertEqual('No schema for config_schema_test.noschema', $e->getMessage());
+    }
+
+    // Test a valid schema.
+    $message = 'Unexpected SchemaIncompleteException thrown';
+    $config = \Drupal::config('config_test.types')->set('int', 10);
+    try {
+      $config->save();
+      $this->pass($message);
+    }
+    catch (SchemaIncompleteException $e) {
+      $this->fail($message);
+    }
+
+    // Test an invalid schema.
+    $message = 'Expected SchemaIncompleteException thrown';
+    $config = \Drupal::config('config_test.types')
+      ->set('foo', 'bar')
+      ->set('array', 1);
+    try {
+      $config->save();
+      $this->fail($message);
+    }
+    catch (SchemaIncompleteException $e) {
+      $this->pass($message);
+      $this->assertEqual('Schema errors for config_test.types with the following errors: config_test.types:foo missing schema, config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence', $e->getMessage());
+    }
+  }
+
+}
diff --git a/core/modules/config/src/Tests/SchemaConfigListenerWebTest.php b/core/modules/config/src/Tests/SchemaConfigListenerWebTest.php
new file mode 100644
index 0000000..10ddc7b
--- /dev/null
+++ b/core/modules/config/src/Tests/SchemaConfigListenerWebTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Tests\SchemaConfigListenerWebTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\Schema\SchemaCheckTrait;
+use Drupal\Core\Config\Schema\SchemaIncompleteException;
+use Drupal\simpletest\KernelTestBase;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the functionality of ConfigSchemaChecker in WebTestBase tests.
+ *
+ * @group config
+ */
+class SchemaConfigListenerWebTest extends WebTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('config_test');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $strictConfigSchema = TRUE;
+
+  /**
+   * Tests \Drupal\Core\Config\Testing\ConfigSchemaChecker.
+   */
+  public function testConfigSchemaChecker() {
+    // Test a non-existing schema.
+    $msg = 'Expected SchemaIncompleteException thrown';
+    try {
+      \Drupal::config('config_schema_test.noschema')->set('foo', 'bar')->save();
+      $this->fail($msg);
+    }
+    catch (SchemaIncompleteException $e) {
+      $this->pass($msg);
+      $this->assertEqual('No schema for config_schema_test.noschema', $e->getMessage());
+    }
+
+    // Test a valid schema.
+    $msg = 'Unexpected SchemaIncompleteException thrown';
+    $config = \Drupal::config('config_test.types')->set('int', 10);
+    try {
+      $config->save();
+      $this->pass($msg);
+    }
+    catch (SchemaIncompleteException $e) {
+      $this->fail($msg);
+    }
+
+    // Test an invalid schema.
+    $msg = 'Expected SchemaIncompleteException thrown';
+    $config = \Drupal::config('config_test.types')
+      ->set('foo', 'bar')
+      ->set('array', 1);
+    try {
+      $config->save();
+      $this->fail($msg);
+    }
+    catch (SchemaIncompleteException $e) {
+      $this->pass($msg);
+      $this->assertEqual('Schema errors for config_test.types with the following errors: config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence, config_test.types:foo missing schema', $e->getMessage());
+    }
+
+    // Test that the config event listener is working in the child site.
+    $this->drupalGet('config_test/schema_listener');
+    $this->assertText('No schema for config_schema_test.noschema');
+  }
+
+}
diff --git a/core/modules/config/tests/config_test/config_test.routing.yml b/core/modules/config/tests/config_test/config_test.routing.yml
index 4873d20..f536326 100644
--- a/core/modules/config/tests/config_test/config_test.routing.yml
+++ b/core/modules/config/tests/config_test/config_test.routing.yml
@@ -58,3 +58,10 @@ entity.config_test.delete_form_config_test_no_status:
     _entity_form: 'config_test_no_status.delete'
   requirements:
     _access: 'TRUE'
+
+config_test.schema_listener:
+  path: '/config_test/schema_listener'
+  defaults:
+    _content: '\Drupal\config_test\SchemaListenerController::test'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/config/tests/config_test/src/SchemaListenerController.php b/core/modules/config/tests/config_test/src/SchemaListenerController.php
new file mode 100644
index 0000000..fe91ade
--- /dev/null
+++ b/core/modules/config/tests/config_test/src/SchemaListenerController.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_test\SchemaListenerController.
+ */
+
+namespace Drupal\config_test;
+
+use Drupal\Core\Config\Schema\SchemaIncompleteException;
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Controller for testing \Drupal\Core\Config\Testing\ConfigSchemaChecker.
+ */
+class SchemaListenerController extends ControllerBase {
+
+  /**
+   * Tests the WebTestBase tests can use strict schema checking.
+   */
+  public function test() {
+    try {
+      $this->config('config_schema_test.noschema')->set('foo', 'bar')->save();
+    }
+    catch (SchemaIncompleteException $e) {
+      return [
+        '#markup' => $e->getMessage(),
+      ];
+    }
+  }
+
+}
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index e281988..d947caa 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -263,6 +263,13 @@ public function containerBuild(ContainerBuilder $container) {
       ->addArgument(Database::getConnection())
       ->addArgument('config');
 
+    if ($this->strictConfigSchema) {
+      $container
+        ->register('simpletest.config_schema_checker', 'Drupal\Core\Config\Testing\ConfigSchemaChecker')
+        ->addArgument(new Reference('config.typed'))
+        ->addTag('event_subscriber');
+    }
+
     $keyvalue_options = $container->getParameter('factory.keyvalue') ?: array();
     $keyvalue_options['default'] = 'keyvalue.memory';
     $container->setParameter('factory.keyvalue', $keyvalue_options);
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index 9475715..e822adb 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -203,6 +203,15 @@
   protected $originalSessionName;
 
   /**
+   * Set to TRUE to strict check all configuration saved.
+   *
+   * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
+   *
+   * @var bool
+   */
+  protected $strictConfigSchema = FALSE;
+
+  /**
    * Constructor for Test.
    *
    * @param $test_id
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 730f84f..a8568d6 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -821,7 +821,17 @@ protected function setUp() {
       // Copy the testing-specific service overrides in place.
       copy($settings_services_file, $directory . '/services.yml');
     }
-
+    if ($this->strictConfigSchema) {
+      // Add a listener to validate configuration schema on save.
+      $yaml = new \Symfony\Component\Yaml\Yaml();
+      $services = $yaml->parse($directory . '/services.yml');
+      $services['services']['simpletest.config_schema_checker'] = [
+        'class' => 'Drupal\Core\Config\Testing\ConfigSchemaChecker',
+        'arguments' => ['@config.typed'],
+        'tags' => [['name' => 'event_subscriber']]
+      ];
+      file_put_contents($directory . '/services.yml', $yaml->dump($services));
+    }
     // Since Drupal is bootstrapped already, install_begin_request() will not
     // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
     // reload the newly written custom settings.php manually.
diff --git a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
index a923e9b..39c403c 100644
--- a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -307,15 +307,15 @@ public function testLoadByProperties() {
       array('foo' => 'bar'),
       array(0 => 'wrong'),
     );
-    $msg = 'An invalid property name throws an exception.';
+    $message = 'An invalid property name throws an exception.';
     foreach ($tests as $properties) {
       try {
         $this->treeStorage->loadByProperties($properties);
-        $this->fail($msg);
+        $this->fail($message);
       }
       catch (\InvalidArgumentException $e) {
         $this->assertTrue(preg_match('/^An invalid property name, .+ was specified. Allowed property names are:/', $e->getMessage()), 'Found expected exception message.');
-        $this->pass($msg);
+        $this->pass($message);
       }
     }
     $this->addMenuLink('test_link.1', '', 'test', array(), 'menu1');
