diff --git a/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php b/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php
index 56740ca..26ec4f3 100644
--- a/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php
+++ b/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php
@@ -8,7 +8,7 @@
 namespace Drupal\dblog\Plugin\rest\resource;
 
 use Drupal\rest\Plugin\ResourceBase;
-use Drupal\rest\ResourceResponse;
+use Drupal\rest\CacheableResourceResponse;
 use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
@@ -33,7 +33,7 @@ class DBLogResource extends ResourceBase {
    * @param int $id
    *   The ID of the watchdog log entry.
    *
-   * @return \Drupal\rest\ResourceResponse
+   * @return \Drupal\rest\CacheableResourceResponse
    *   The response containing the log entry.
    *
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
@@ -43,7 +43,7 @@ public function get($id = NULL) {
       $record = db_query("SELECT * FROM {watchdog} WHERE wid = :wid", array(':wid' => $id))
         ->fetchAssoc();
       if (!empty($record)) {
-        return new ResourceResponse($record);
+        return new CacheableResourceResponse($record);
       }
 
       throw new NotFoundHttpException(t('Log entry with ID @id was not found', array('@id' => $id)));
diff --git a/core/modules/rest/src/CacheableResourceResponse.php b/core/modules/rest/src/CacheableResourceResponse.php
new file mode 100644
index 0000000..9f58244
--- /dev/null
+++ b/core/modules/rest/src/CacheableResourceResponse.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\rest\CacheableResourceResponse.
+ */
+
+namespace Drupal\rest;
+
+use Drupal\Core\Cache\CacheableResponseInterface;
+use Drupal\Core\Cache\CacheableResponseTrait;
+use Symfony\Component\HttpFoundation\Response;
+
+class CacheableResourceResponse extends Response implements CacheableResponseInterface, ResourceResponseInterface {
+
+  use CacheableResponseTrait;
+  use ResourceResponseTrait;
+
+  /**
+   * Constructor for CacheableResourceResponse objects.
+   *
+   * @param mixed $data
+   *   Response data that should be serialized.
+   * @param int $status
+   *   The response status code.
+   * @param array $headers
+   *   An array of response headers.
+   */
+  public function __construct($data = NULL, $status = 200, $headers = array()) {
+    $this->responseData = $data;
+    parent::__construct('', $status, $headers);
+  }
+
+}
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 4b4d027..39f5d1a 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -9,8 +9,9 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
+use Drupal\rest\CacheableResourceResponse;
 use Drupal\rest\Plugin\ResourceBase;
-use Drupal\rest\ResourceResponse;
+use Drupal\rest\UncacheableResourceResponse;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -39,7 +40,7 @@ class EntityResource extends ResourceBase {
    * @param \Drupal\Core\Entity\EntityInterface $entity
    *   The entity object.
    *
-   * @return \Drupal\rest\ResourceResponse
+   * @return \Drupal\rest\UncacheableResourceResponse
    *   The response containing the entity with its accessible fields.
    *
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
@@ -54,7 +55,7 @@ public function get(EntityInterface $entity) {
       }
     }
 
-    $response = new ResourceResponse($entity, 200);
+    $response = new CacheableResourceResponse($entity, 200);
     // Make the response use the entity's cacheability metadata.
     // @todo include access cacheability metadata, for the access checks above.
     $response->addCacheableDependency($entity);
@@ -109,8 +110,7 @@ public function post(EntityInterface $entity = NULL) {
 
       // 201 Created responses have an empty body.
       $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
-      $response = new ResourceResponse(NULL, 201, ['Location' => $url->getGeneratedUrl()]);
-      $response->addCacheableDependency($url);
+      $response = new UncacheableResourceResponse(NULL, 201, ['Location' => $url->getGeneratedUrl()]);
       return $response;
     }
     catch (EntityStorageException $e) {
@@ -166,7 +166,7 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity
       $this->logger->notice('Updated entity %type with ID %id.', array('%type' => $original_entity->getEntityTypeId(), '%id' => $original_entity->id()));
 
       // Update responses have an empty body.
-      return new ResourceResponse(NULL, 204);
+      return new UncacheableResourceResponse(NULL, 204);
     }
     catch (EntityStorageException $e) {
       throw new HttpException(500, 'Internal Server Error', $e);
@@ -193,7 +193,7 @@ public function delete(EntityInterface $entity) {
       $this->logger->notice('Deleted entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id()));
 
       // Delete responses have an empty body.
-      return new ResourceResponse(NULL, 204);
+      return new UncacheableResourceResponse(NULL, 204);
     }
     catch (EntityStorageException $e) {
       throw new HttpException(500, 'Internal Server Error', $e);
diff --git a/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php
new file mode 100644
index 0000000..762e2f5
--- /dev/null
+++ b/core/modules/rest/src/Plugin/rest/resource/UserRegistrationResource.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\rest\Plugin\rest\resource\UserRegistrationResource.
+ */
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\rest\UncacheableResourceResponse;
+use Drupal\user\UserInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Drupal\rest\Plugin\ResourceBase;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+/**
+ * Represents user registration as resource.
+ *
+ * @RestResource(
+ *   id = "rest_user_registration",
+ *   label = @Translation("User Registration"),
+ *   serialization_class = "Drupal\user\Entity\User"
+ * )
+ */
+class UserRegistrationResource extends ResourceBase {
+
+  /**
+   * User settings config instance.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $userSettings;
+
+  /**
+   * Current user object.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * Constructs a new UserRegistrationResource instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param array $serializer_formats
+   *   The available serialization formats.
+   * @param LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\Core\Config\ImmutableConfig $user_settings
+   *   A user settings config instance.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   A user settings config instance.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, ImmutableConfig $user_settings, AccountInterface $current_user) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
+    $this->userSettings = $user_settings;
+    $this->currentUser = $current_user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->getParameter('serializer.formats'),
+      $container->get('logger.factory')->get('rest'),
+      $container->get('config.factory')->get('user.settings'),
+      $container->get('current_user')
+    );
+  }
+
+  /**
+   * Responds to user registration POST request.
+   *
+   * @param \Drupal\user\UserInterface $account
+   *   The user account entity.
+   *
+   * @return \Drupal\rest\UncacheableResourceResponse
+   *   The HTTP response object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function post(UserInterface $account = NULL) {
+    if ($account == NULL) {
+      throw new BadRequestHttpException('No user account data for registration received.');
+    }
+
+    // POSTed user accounts must not have an ID set, because we always
+    // want to create new entities here.
+    if (!$account->isNew()) {
+      throw new BadRequestHttpException('An ID has been set and only new user accounts can be registered.');
+    }
+
+    // The current resource only allows anonymous users to register users.
+    if (!$this->currentUser->isAnonymous()) {
+      throw new AccessDeniedHttpException('Only anonymous users can register users.');
+    }
+    $approvalSettings = $this->userSettings->get('register');
+    // Verify that the current user can register a user account.
+    if ($approvalSettings == USER_REGISTER_ADMINISTRATORS_ONLY) {
+      throw new AccessDeniedHttpException('Only administrators can register users.');
+    }
+    // If current user can register accounts then let's block the new registered
+    // user if admin approval is needed.
+    elseif ($approvalSettings == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) {
+      $account->block();
+    }
+
+    // Only check 'edit' permissions for fields that were actually submitted by
+    // the user. Field access makes no difference between 'create'and 'update',
+    // so the 'edit' operation is used here.
+    foreach ($account->_restSubmittedFields as $key => $field_name) {
+      if (!$account->get($field_name)->access('edit')) {
+        throw new AccessDeniedHttpException("Access denied on creating field '$field_name'.");
+      }
+    }
+
+    // Make sure that the user entity is valid (email and name are valid).
+    $this->validate($account);
+    // Create the account.
+    $account->save();
+
+    $register = $this->userSettings->get('register');
+    // No E-mail verification is required. Activating the user.
+    if ($register == 'visitors') {
+      if (!$this->userSettings->get('verify_mail')) {
+        // Notification will be sent if activated.
+        $account->activate();
+        // Save changes to apply active status to the account.
+        $account->save();
+      }
+      // No administrator approval required.
+      else {
+        _user_mail_notify('register_no_approval_required', $account);
+      }
+    }
+    // Administrator approval required.
+    elseif ($register == 'visitors_admin_approval') {
+      _user_mail_notify('register_pending_approval', $account);
+    }
+
+    $url = $account->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
+    $response = new UncacheableResourceResponse(NULL, 201, ['Location' => $url->getGeneratedUrl()]);
+
+    return $response;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function routes() {
+    $collection = new RouteCollection();
+
+    $route = $this->getBaseRoute('/entity/user/register', 'POST');
+
+    // Restrict the incoming HTTP Content-type header to the known serialization
+    // formats.
+    $route->addRequirements(['_content_type_format' => implode('|', $this->serializerFormats)]);
+    $collection->add("$this->pluginId", $route);
+
+    return $collection;
+  }
+
+  /**
+   * Verifies that the whole entity does not violate any validation constraints.
+   *
+   * @param \Drupal\user\UserInterface $entity
+   *   The entity object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   *   If validation errors are found.
+   */
+  protected function validate(UserInterface $entity) {
+    $violations = $entity->validate();
+
+    // Remove violations of inaccessible fields as they cannot stem from our
+    // changes.
+    $violations->filterByFieldAccess();
+
+    if (count($violations) > 0) {
+      $message = "Unprocessable Entity: validation failed.\n";
+      foreach ($violations as $violation) {
+        $message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n";
+      }
+      // Instead of returning a generic 400 response we use the more specific
+      // 422 Unprocessable Entity code from RFC 4918. That way clients can
+      // distinguish between general syntax errors in bad serializations (code
+      // 400) and semantic errors in well-formed requests (code 422).
+      throw new HttpException(422, $message);
+    }
+  }
+
+}
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 5a04cd8..6dcb39f 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\rest;
 
+use Drupal\Core\Cache\CacheableResponseInterface;
 use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Symfony\Component\DependencyInjection\ContainerAwareInterface;
@@ -103,26 +104,35 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
     }
 
     // Serialize the outgoing data for the response, if available.
-    if ($response instanceof ResourceResponse && $data = $response->getResponseData()) {
-      // Serialization can invoke rendering (e.g., generating URLs), but the
-      // serialization API does not provide a mechanism to collect the
-      // bubbleable metadata associated with that (e.g., language and other
-      // contexts), so instead, allow those to "leak" and collect them here in
-      // a render context.
-      // @todo Add test coverage for language negotiation contexts in
-      //   https://www.drupal.org/node/2135829.
-      $context = new RenderContext();
-      $output = $this->container->get('renderer')->executeInRenderContext($context, function() use ($serializer, $data, $format) {
-        return $serializer->serialize($data, $format);
-      });
-      $response->setContent($output);
-      if (!$context->isEmpty()) {
-        $response->addCacheableDependency($context->pop());
-      }
+    if ($response instanceof ResourceResponseInterface && $data = $response->getResponseData()) {
+      // Cacheable Response.
+      if ($response instanceof CacheableResponseInterface) {
+        // Serialization can invoke rendering (e.g., generating URLs), but the
+        // serialization API does not provide a mechanism to collect the
+        // bubbleable metadata associated with that (e.g., language and other
+        // contexts), so instead, allow those to "leak" and collect them here in
+        // a render context.
+        // @todo Add test coverage for language negotiation contexts in
+        //   https://www.drupal.org/node/2135829.
+        $context = new RenderContext();
+        $output = $this->container->get('renderer')->executeInRenderContext($context, function () use ($serializer, $data, $format) {
+          return $serializer->serialize($data, $format);
+        });
+        $response->setContent($output);
+        if (!$context->isEmpty()) {
+          $response->addCacheableDependency($context->pop());
+        }
 
-      $response->headers->set('Content-Type', $request->getMimeType($format));
-      // Add rest settings config's cache tags.
-      $response->addCacheableDependency($this->container->get('config.factory')->get('rest.settings'));
+        $response->headers->set('Content-Type', $request->getMimeType($format));
+        // Add rest settings config's cache tags.
+        $response->addCacheableDependency($this->container->get('config.factory')->get('rest.settings'));
+      }
+      else {
+        // Uncacheable Response.
+        $output = $serializer->serialize($data, $format);
+        $response->setContent($output);
+        $response->headers->set('Content-Type', $request->getMimeType($format));
+      }
     }
     return $response;
   }
diff --git a/core/modules/rest/src/ResourceResponse.php b/core/modules/rest/src/ResourceResponse.php
deleted file mode 100644
index 2919fb1..0000000
--- a/core/modules/rest/src/ResourceResponse.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\rest\ResourceResponse.
- */
-
-namespace Drupal\rest;
-
-use Drupal\Core\Cache\CacheableResponseInterface;
-use Drupal\Core\Cache\CacheableResponseTrait;
-use Symfony\Component\HttpFoundation\Response;
-
-/**
- * Contains data for serialization before sending the response.
- *
- * We do not want to abuse the $content property on the Response class to store
- * our response data. $content implies that the provided data must either be a
- * string or an object with a __toString() method, which is not a requirement
- * for data used here.
- */
-class ResourceResponse extends Response implements CacheableResponseInterface {
-
-  use CacheableResponseTrait;
-
-  /**
-   * Response data that should be serialized.
-   *
-   * @var mixed
-   */
-  protected $responseData;
-
-  /**
-   * Constructor for ResourceResponse objects.
-   *
-   * @param mixed $data
-   *   Response data that should be serialized.
-   * @param int $status
-   *   The response status code.
-   * @param array $headers
-   *   An array of response headers.
-   */
-  public function __construct($data = NULL, $status = 200, $headers = array()) {
-    $this->responseData = $data;
-    parent::__construct('', $status, $headers);
-  }
-
-  /**
-   * Returns response data that should be serialized.
-   *
-   * @return mixed
-   *   Response data that should be serialized.
-   */
-  public function getResponseData() {
-    return $this->responseData;
-  }
-}
diff --git a/core/modules/rest/src/ResourceResponseInterface.php b/core/modules/rest/src/ResourceResponseInterface.php
new file mode 100644
index 0000000..a889dae
--- /dev/null
+++ b/core/modules/rest/src/ResourceResponseInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\rest\ResourceResponseInterface.
+ */
+
+namespace Drupal\rest;
+
+/**
+ * Defines a common interface for Resource Responses.
+ */
+interface ResourceResponseInterface {
+
+  /**
+   * Returns response data that should be serialized.
+   *
+   * @return mixed
+   *   Response data that should be serialized.
+   */
+  public function getResponseData();
+}
+
diff --git a/core/modules/rest/src/ResourceResponseTrait.php b/core/modules/rest/src/ResourceResponseTrait.php
new file mode 100644
index 0000000..0539df6
--- /dev/null
+++ b/core/modules/rest/src/ResourceResponseTrait.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\rest\ResourceResponseTrait.
+ */
+
+namespace Drupal\rest;
+
+
+Trait ResourceResponseTrait {
+
+  /**
+   * Response data that should be serialized.
+   *
+   * @var mixed
+   */
+  protected $responseData;
+
+  /**
+   * Returns response data that should be serialized.
+   *
+   * @return mixed
+   *   Response data that should be serialized.
+   */
+  public function getResponseData() {
+    return $this->responseData;
+  }
+
+}
diff --git a/core/modules/rest/src/Tests/RegisterUserTest.php b/core/modules/rest/src/Tests/RegisterUserTest.php
new file mode 100644
index 0000000..a5e2d58
--- /dev/null
+++ b/core/modules/rest/src/Tests/RegisterUserTest.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\rest\Tests\RegisterUserTest.
+ */
+
+namespace Drupal\rest\Tests;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+
+
+/**
+ * Tests User Registration.
+ *
+ * @group rest
+ */
+class RegisterUserTest extends RESTTestBase {
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['hal'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    // Enabling services for the user registration and GET the new user entity
+    // created.
+    $config = $this->config('rest.settings');
+    $settings = [];
+    $resources = [
+      ['type' => 'rest_user_registration', 'method' => 'POST'],
+    ];
+    $format = 'hal_json';
+    $auth = $this->defaultAuth;
+    foreach ($resources as $resource) {
+      $settings[$resource['type']][$resource['method']]['supported_formats'][] = $format;
+      $settings[$resource['type']][$resource['method']]['supported_auth'] = $auth;
+      $config->set('resources', $settings);
+      $config->save();
+    }
+    $this->rebuildCache();
+
+    // Add registration permission to anonymous user.
+    Role::load(RoleInterface::ANONYMOUS_ID)
+      ->grantPermission('restful post rest_user_registration')
+      ->save();
+  }
+
+  /**
+   * Tests user registration from REST.
+   */
+  protected function testRegisterUser() {
+
+    // New user info to be serialized.
+    $data = [
+      "_links" =>
+        [
+          "type" => ["href" => $GLOBALS['base_url'] . "/rest/type/user/user"]
+        ],
+      "langcode" =>  [
+        [
+          "value" => "en"
+        ]
+      ],
+      "name" => [
+        [
+          "value" => "Druplicon"
+        ]
+      ],
+      "mail" => [
+        [
+          "value" => "druplicon@example.com"
+        ]
+      ],
+      "pass" => [
+        [
+          "value" => "SuperSecretPassword"
+        ]
+      ]
+    ];
+
+    // Create a JSON version for the user entity we want to create.
+    $serialized = $this->container->get('serializer')->serialize($data, 'hal_json');
+
+    // Post to the REST service to register the user.
+    $this->httpRequest('entity/user/register', 'POST', $serialized, 'application/hal+json');
+    $this->assertResponse('201', 'HTTP response code is correct.');
+
+    // Obtain the uid from the header.
+    $url_parts = explode('/', $this->drupalGetHeader('location'));
+    $id = end($url_parts);
+    $this->assertHeader('Location', $GLOBALS['base_url'] . '/user/' . $id);
+
+    $this->assertTrue((bool) $this->container->get('entity.query')
+      ->get('user')
+      ->condition('name', 'Druplicon')
+      ->range(0, 1)
+      ->count()
+      ->execute(), 'The user was created as expected');
+  }
+
+}
diff --git a/core/modules/rest/src/UncacheableResourceResponse.php b/core/modules/rest/src/UncacheableResourceResponse.php
new file mode 100644
index 0000000..308275d
--- /dev/null
+++ b/core/modules/rest/src/UncacheableResourceResponse.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\rest\UncacheableResourceResponse.
+ */
+
+namespace Drupal\rest;
+
+use Symfony\Component\HttpFoundation\Response;
+
+class UncacheableResourceResponse extends Response implements ResourceResponseInterface {
+
+  use ResourceResponseTrait;
+
+  /**
+   * Constructor for UncacheableResourceResponse objects.
+   *
+   * @param mixed $data
+   *   Response data that should be serialized.
+   * @param int $status
+   *   The response status code.
+   * @param array $headers
+   *   An array of response headers.
+   */
+  public function __construct($data = NULL, $status = 200, $headers = []) {
+    $this->responseData = $data;
+    parent::__construct('', $status, $headers);
+  }
+}
diff --git a/core/modules/rest/tests/src/Unit/UserRegistrationResourceTest.php b/core/modules/rest/tests/src/Unit/UserRegistrationResourceTest.php
new file mode 100644
index 0000000..6ac54f0
--- /dev/null
+++ b/core/modules/rest/tests/src/Unit/UserRegistrationResourceTest.php
@@ -0,0 +1,320 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\rest\Unit\UserRegistrationResourceTest.
+ */
+
+namespace Drupal\Tests\rest\Unit;
+
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\Field\FieldItemList;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\rest\Plugin\rest\resource\UserRegistrationResource;
+use Drupal\Tests\UnitTestCase;
+use Drupal\user\Entity\User;
+use Psr\Log\LoggerInterface;
+use Drupal\Core\Entity\EntityConstraintViolationList;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+
+/**
+ * Only administrators can create user accounts.
+ */
+if (!defined('USER_REGISTER_ADMINISTRATORS_ONLY')) {
+  define('USER_REGISTER_ADMINISTRATORS_ONLY', 'admin_only');
+}
+
+/**
+ * Visitors can create their own accounts.
+ */
+if (!defined('USER_REGISTER_VISITORS')) {
+  define('USER_REGISTER_VISITORS', 'visitors');
+}
+
+/**
+ * Visitors can create accounts, but they don't become active without
+ * administrative approval.
+ */
+if (!defined('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL')) {
+  define('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL', 'visitors_admin_approval');
+}
+
+/**
+ * Tests User Registration REST resource.
+ *
+ * @coversDefaultClass \Drupal\Tests\rest\Unit\UserRegistrationResourceTest
+ * @group rest
+ */
+class UserRegistrationResourceTest extends UnitTestCase {
+
+  const ERROR_MESSAGE = "Unprocessable Entity: validation failed.\nproperty_path: message\nproperty_path_2: message_2\n";
+
+  /**
+   * Class to be tested.
+   *
+   * @var \Drupal\rest\Plugin\rest\resource\UserRegistrationResource
+   */
+  protected $testClass;
+
+  /**
+   * A reflection of self::$testClass.
+   *
+   * @var \ReflectionClass
+   */
+  protected $reflection;
+
+  /**
+   * A user settings config instance.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $userSettings;
+
+  /**
+   * Logger service.
+   *
+   * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $logger;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $currentUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->logger = $this->getMock(LoggerInterface::class);
+
+    $this->userSettings = $this->getMockBuilder(ImmutableConfig::class)
+      ->setMethods(['get'])
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->currentUser = $this->getMockBuilder(AccountInterface::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings, $this->currentUser);
+    $this->reflection = new \ReflectionClass($this->testClass);
+
+  }
+
+  /**
+   * Helper method to set a protected method as accessible.
+   *
+   * @param string $method
+   *   The protected method.
+   *
+   * @return \ReflectionMethod
+   */
+  public function getProtectedMethod($method) {
+    $method = $this->reflection->getMethod($method);
+    $method->setAccessible(TRUE);
+
+    return $method;
+  }
+
+  /**
+   * Tests that the user entity does not violate any validation constraints.
+   */
+  public function testValidate() {
+    $violations = $this->getMockBuilder(EntityConstraintViolationList::class)
+      ->setMethods(['filterByFieldAccess'])
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $violations->expects($this->once())
+      ->method('filterByFieldAccess')
+      ->will($this->returnValue(array()));
+
+    $entity = $this->getMockBuilder(User::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $entity->expects($this->once())
+      ->method('validate')
+      ->will($this->returnValue($violations));
+
+    $method = $this->getProtectedMethod('validate');
+
+    // No exception is thrown.
+    $method->invokeArgs($this->testClass, [$entity]);
+  }
+
+  /**
+   * Tests that error validation is thrown as expected.
+   *
+   * @expectedException \Symfony\Component\HttpKernel\Exception\HttpException
+   * @expectedException UserRegistrationResourceTest::ERROR_MESSAGE
+   */
+  public function testFailedValidate() {
+    $violation1 = $this->getMock(ConstraintViolationInterface::class);
+
+    $violation1->expects($this->once())
+      ->method('getPropertyPath')
+      ->will($this->returnValue('property_path'));
+
+    $violation1->expects($this->once())
+      ->method('getMessage')
+      ->will($this->returnValue('message'));
+
+    $violation2 = $this->getMock(ConstraintViolationInterface::class);
+
+    $violation2->expects($this->once())
+      ->method('getPropertyPath')
+      ->will($this->returnValue('property_path_2'));
+
+    $violation2->expects($this->once())
+      ->method('getMessage')
+      ->will($this->returnValue('message_2'));
+
+    $entity = $this->getMockBuilder(User::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $violations = $this->getMockBuilder(EntityConstraintViolationList::class)
+      ->setConstructorArgs([$entity, [$violation1, $violation2]])
+      ->setMethods(['filterByFieldAccess'])
+      ->getMock();
+
+    $violations->expects($this->once())
+      ->method('filterByFieldAccess')
+      ->will($this->returnValue([]));
+
+    $entity->expects($this->once())
+      ->method('validate')
+      ->will($this->returnValue($violations));
+
+    $method = $this->getProtectedMethod('validate');
+
+    $method->invoke($this->testClass, $entity);
+  }
+
+  /**
+   * Tests that an exception is thrown when no data provided for the account.
+   *
+   * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   * @expectedExceptionMessage No user account data for registration received.
+   */
+  public function testEmptyPost() {
+    $this->testClass->post(NULL);
+  }
+
+  /**
+   * Tests that only new user accounts can be registered.
+   *
+   * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   * @expectedExceptionMessage An ID has been set and only new user accounts can be registered.
+   */
+  public function testExistedEntityPost() {
+    $entity = $this->getMockBuilder(User::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $entity->expects($this->once())
+      ->method('isNew')
+      ->will($this->returnValue(FALSE));
+
+    $this->testClass->post($entity);
+  }
+
+  /**
+   * Tests that admin permissions are required to register a user account.
+   *
+   * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @expectedExceptionMessage Only administrators can register users.
+   */
+  public function testRegistrationAdminOnlyPost() {
+    $this->currentUser->expects($this->once())
+      ->method('isAnonymous')
+      ->will($this->returnValue(TRUE));
+
+    $this->userSettings->expects($this->once())
+      ->method('get')
+      ->will($this->returnValue(USER_REGISTER_ADMINISTRATORS_ONLY));
+
+    $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings, $this->currentUser);
+    $entity = $this->getMockBuilder(User::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $entity->expects($this->once())
+      ->method('isNew')
+      ->will($this->returnValue(TRUE));
+
+    $this->testClass->post($entity);
+  }
+
+  /**
+   * Tests that only anonymous users can register users.
+   *
+   * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @expectedExceptionMessage Only anonymous users can register users.
+   */
+  public function testRegistrationAnonymousOnlyPost() {
+    $this->currentUser->expects($this->once())
+      ->method('isAnonymous')
+      ->will($this->returnValue(FALSE));
+
+    $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings, $this->currentUser);
+
+    $entity = $this->getMockBuilder(User::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $entity->expects($this->once())
+      ->method('isNew')
+      ->will($this->returnValue(TRUE));
+
+    $this->testClass->post($entity);
+  }
+
+  /**
+   * Tests access denied on creating field.
+   *
+   * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @expectedExceptionMessage Access denied on creating field 'test_field'.
+   */
+  public function testFieldAccessValidation() {
+    $this->currentUser->expects($this->once())
+      ->method('isAnonymous')
+      ->will($this->returnValue(TRUE));
+
+    $this->testClass = new UserRegistrationResource([], 'plugin_id', '', [], $this->logger, $this->userSettings, $this->currentUser);
+
+    $entity = $this->getMockBuilder(User::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $entity->expects($this->once())
+      ->method('isNew')
+      ->will($this->returnValue(TRUE));
+
+    $field = $this->getMockBuilder(FieldItemList::class)
+      ->setMethods(['access'])
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $field->expects($this->once())
+      ->method('access')
+      ->will($this->returnValue(FALSE));
+
+    $entity->expects($this->once())
+      ->method('get')
+      ->will($this->returnValue($field));
+
+    $entity->expects($this->once())
+      ->method('__get')
+      ->will($this->returnValue(['test' => 'test_field']));
+
+    $this->testClass->post($entity);
+  }
+
+}
