diff --git a/src/ReadinessChecker/Filesystem.php b/src/ReadinessChecker/Filesystem.php
index 2edc02f181d019d1a8939d51f15022895cbed492..de0914ff2a350c1b728669daa3c9d0d751ea05a4 100644
--- a/src/ReadinessChecker/Filesystem.php
+++ b/src/ReadinessChecker/Filesystem.php
@@ -38,7 +38,7 @@ abstract class Filesystem implements ReadinessCheckerInterface {
    * {@inheritdoc}
    */
   public function run() {
-    if (!$this->exists($this->getRootPath() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, ['core', 'core.api.php']))) {
+    if (!file_exists($this->getRootPath() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, ['core', 'core.api.php']))) {
       return [$this->t('The web root could not be located.')];
     }
 
@@ -96,17 +96,4 @@ abstract class Filesystem implements ReadinessCheckerInterface {
     return $root_statistics && $vendor_statistics && $root_statistics['dev'] === $vendor_statistics['dev'];
   }
 
-  /**
-   * Checks whether a file or directory exists.
-   *
-   * @param string $file
-   *   The file path to test.
-   *
-   * @return bool
-   *   TRUE if the file exists, otherwise FALSE.
-   */
-  protected function exists($file) {
-    return file_exists($file);
-  }
-
 }
diff --git a/src/ReadinessChecker/Vendor.php b/src/ReadinessChecker/Vendor.php
index 08aaa736e7a1194ec5c64f243e00e5fad8ab5bd5..3529b72c0e7ee039d4aa639f255f1c14f4c52f0b 100644
--- a/src/ReadinessChecker/Vendor.php
+++ b/src/ReadinessChecker/Vendor.php
@@ -11,7 +11,7 @@ class Vendor extends Filesystem {
    * {@inheritdoc}
    */
   protected function doCheck() {
-    if (!$this->exists($this->getVendorPath() . DIRECTORY_SEPARATOR . 'autoload.php')) {
+    if (!file_exists($this->getVendorPath() . DIRECTORY_SEPARATOR . 'autoload.php')) {
       return [$this->t('The vendor folder could not be located.')];
     }
     return [];
diff --git a/tests/src/Kernel/ReadinessChecker/ReadOnlyFilesystemTest.php b/tests/src/Kernel/ReadinessChecker/ReadOnlyFilesystemTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..acff7f82a2c7f712cec0a956f3c95143e68b41bb
--- /dev/null
+++ b/tests/src/Kernel/ReadinessChecker/ReadOnlyFilesystemTest.php
@@ -0,0 +1,216 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel\ReadinessChecker;
+
+use Drupal\automatic_updates\ReadinessChecker\ReadOnlyFilesystem;
+use Drupal\Core\File\Exception\FileException;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\KernelTests\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\automatic_updates\ReadinessChecker\ReadOnlyFilesystem
+ *
+ * @group automatic_updates
+ */
+class ReadOnlyFilesystemTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'automatic_updates',
+    'system',
+  ];
+
+  /**
+   * Tests the readiness check where the root directory does not exist.
+   *
+   * @covers ::run
+   *
+   * @dataProvider providerNoWebRoot
+   */
+  public function testNoWebRoot($files) {
+    vfsStream::setup('root');
+    vfsStream::create($files);
+    $readOnly = new ReadOnlyFilesystem(
+      vfsStream::url('root'),
+      $this->prophesize(LoggerInterface::class)->reveal(),
+      $this->prophesize(FileSystemInterface::class)->reveal()
+    );
+    $this->assertEquals(['The web root could not be located.'], $readOnly->run());
+  }
+
+  /**
+   * Data provider for testNoWebRoot().
+   */
+  public function providerNoWebRoot() {
+    return [
+      'no core.api.php' => [
+        [
+          'core' => [
+            'core.txt' => 'test',
+          ],
+        ],
+      ],
+      'core.api.php in wrong location' => [
+        [
+          'core.api.php' => 'test',
+        ],
+      ],
+
+    ];
+  }
+
+  /**
+   * Tests the readiness check on writable file system on same logic disk.
+   *
+   * @covers ::run
+   */
+  public function testSameLogicDiskWritable() {
+    $readOnly = new ReadOnlyFilesystem(
+      self::getVfsRoot(),
+      $this->container->get('logger.channel.automatic_updates'),
+      $this->container->get('file_system')
+    );
+    $this->assertEquals([], $readOnly->run());
+  }
+
+  /**
+   * Tests root and vendor directories are writable on different logical disks.
+   *
+   * @covers ::run
+   */
+  public function testDifferentLogicDiskWritable() {
+    $readOnly = new TestReadOnlyFilesystem(
+      self::getVfsRoot(),
+      $this->container->get('logger.channel.automatic_updates'),
+      $this->container->get('file_system')
+    );
+    $this->assertEquals([], $readOnly->run());
+  }
+
+  /**
+   * Tests non-writable core and vendor directories on same logic disk.
+   *
+   * @covers ::run
+   */
+  public function testSameLogicDiskNotWritable() {
+    $file_system = $this->createMock(FileSystemInterface::class);
+    $file_system->expects($this->once())
+      ->method('copy')
+      ->willThrowException(new FileException());
+
+    $root = self::getVfsRoot();
+    $readOnly = new ReadOnlyFilesystem(
+      $root,
+      $this->container->get('logger.channel.automatic_updates'),
+      $file_system
+    );
+    $this->assertEquals(["Logical disk at \"$root\" is read only. Updates to Drupal cannot be applied against a read only file system."], $readOnly->run());
+  }
+
+  /**
+   * Tests the readiness check on read-only file system.
+   *
+   * @covers ::run
+   */
+  public function testDifferentLogicDiskNotWritable() {
+    $root = self::getVfsRoot();
+
+    // Assert messages if both core and vendor directory are not writable.
+    $file_system = $this->createMock(FileSystemInterface::class);
+    $file_system->expects($this->any())
+      ->method('copy')
+      ->willThrowException(new FileException());
+    $readOnly = new TestReadOnlyFileSystem(
+      $root,
+      $this->container->get('logger.channel.automatic_updates'),
+      $file_system
+    );
+    $this->assertEquals(
+      [
+        "Drupal core filesystem at \"$root/core\" is read only. Updates to Drupal core cannot be applied against a read only file system.",
+        "Vendor filesystem at \"$root/vendor\" is read only. Updates to the site's code base cannot be applied against a read only file system.",
+      ],
+      $readOnly->run()
+    );
+
+    // Assert messages if core directory is not writable.
+    $file_system = $this->createMock(FileSystemInterface::class);
+    $file_system
+      ->method('copy')
+      ->withConsecutive(
+        ['vfs://root/core/core.api.php', 'vfs://root/core/core.api.php.automatic_updates'],
+        ['vfs://root/vendor/composer/LICENSE', 'vfs://root/vendor/composer/LICENSE.automatic_updates']
+      )
+      ->willReturnOnConsecutiveCalls(FALSE, TRUE);
+    $readOnly = new TestReadOnlyFileSystem(
+      $root,
+      $this->container->get('logger.channel.automatic_updates'),
+      $file_system
+    );
+    $this->assertEquals(
+      ["Drupal core filesystem at \"$root/core\" is read only. Updates to Drupal core cannot be applied against a read only file system."],
+      $readOnly->run()
+    );
+
+    // Assert messages if vendor directory is not writable.
+    $file_system = $this->createMock(FileSystemInterface::class);
+    $file_system
+      ->method('copy')
+      ->withConsecutive(
+        ['vfs://root/core/core.api.php', 'vfs://root/core/core.api.php.automatic_updates'],
+        ['vfs://root/vendor/composer/LICENSE', 'vfs://root/vendor/composer/LICENSE.automatic_updates']
+      )
+      ->willReturnOnConsecutiveCalls(TRUE, FALSE);
+    $readOnly = new TestReadOnlyFileSystem(
+      $root,
+      $this->container->get('logger.channel.automatic_updates'),
+      $file_system
+    );
+    $this->assertEquals(
+      ["Vendor filesystem at \"$root/vendor\" is read only. Updates to the site's code base cannot be applied against a read only file system."],
+      $readOnly->run()
+    );
+  }
+
+  /**
+   * Gets root of virtual Drupal directory.
+   *
+   * @return string
+   *   The root.
+   */
+  protected static function getVfsRoot() {
+    vfsStream::setup('root');
+    vfsStream::create([
+      'core' => [
+        'core.api.php' => 'test',
+      ],
+      'vendor' => [
+        'composer' => [
+          'LICENSE' => 'test',
+        ],
+      ],
+    ]);
+    return vfsStream::url('root');
+  }
+
+}
+
+/**
+ * Test class to root and vendor directories in different logic disks.
+ *
+ * Calls to stat() does not work on \org\bovigo\vfs\vfsStream.
+ */
+class TestReadOnlyFileSystem extends ReadOnlyFilesystem {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function areSameLogicalDisk($root, $vendor) {
+    return FALSE;
+  }
+
+}
diff --git a/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php b/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php
deleted file mode 100644
index 567a1f63c39d8165bc551a33ba319e3b4beb747b..0000000000000000000000000000000000000000
--- a/tests/src/Kernel/ReadinessChecker/ReadOnlyTest.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-namespace Drupal\Tests\automatic_updates\Kernel\ReadinessChecker;
-
-use Drupal\automatic_updates\ReadinessChecker\ReadOnlyFilesystem;
-use Drupal\Core\File\Exception\FileException;
-use Drupal\Core\File\Exception\FileWriteException;
-use Drupal\Core\File\FileSystemInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\KernelTests\KernelTestBase;
-use Psr\Log\LoggerInterface;
-
-/**
- * Tests read only readiness checking.
- *
- * @group automatic_updates
- */
-class ReadOnlyTest extends KernelTestBase {
-  use StringTranslationTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = [
-    'automatic_updates',
-    'system',
-  ];
-
-  /**
-   * Tests the functionality of read only filesystem readiness checks.
-   */
-  public function testReadOnly() {
-    $messages = $filesystem = $this->container->get('automatic_updates.readonly_checker')->run();
-    $this->assertEmpty($messages);
-
-    $filesystem = $this->createMock(FileSystemInterface::class);
-    $filesystem
-      ->method('copy')
-      ->withAnyParameters()
-      ->will($this->onConsecutiveCalls(
-        $this->throwException(new FileWriteException('core.api.php')),
-        $this->throwException(new FileWriteException('core.api.php')),
-        $this->throwException(new FileWriteException('composer/LICENSE')),
-        'full/file/path',
-        'full/file/path'
-      )
-    );
-    $filesystem
-      ->method('delete')
-      ->withAnyParameters()
-      ->will($this->onConsecutiveCalls(
-        $this->throwException(new FileException('delete failed.')),
-        $this->throwException(new FileException('delete failed.'))
-      )
-    );
-
-    $app_root = $this->container->get('app.root');
-    $readonly = $this->getMockBuilder(ReadOnlyFilesystem::class)
-      ->setConstructorArgs([
-        $app_root,
-        $this->createMock(LoggerInterface::class),
-        $filesystem,
-      ])
-      ->setMethods([
-        'areSameLogicalDisk',
-        'exists',
-      ])
-      ->getMock();
-    $readonly
-      ->method('areSameLogicalDisk')
-      ->withAnyParameters()
-      ->will($this->onConsecutiveCalls(
-        TRUE,
-        FALSE,
-        FALSE
-      )
-    );
-    $readonly
-      ->method('exists')
-      ->withAnyParameters()
-      ->will($this->onConsecutiveCalls(
-        FALSE,
-        TRUE,
-        TRUE,
-        TRUE
-      )
-    );
-
-    // Test can't locate drupal.
-    $messages = $readonly->run();
-    self::assertEquals([$this->t('The web root could not be located.')], $messages);
-
-    // Test same logical disk.
-    $expected_messages = [];
-    $expected_messages[] = $this->t('Logical disk at "@app_root" is read only. Updates to Drupal cannot be applied against a read only file system.', ['@app_root' => $app_root]);
-    $messages = $readonly->run();
-    self::assertEquals($expected_messages, $messages);
-
-    // Test read-only.
-    $expected_messages = [];
-    $expected_messages[] = $this->t('Drupal core filesystem at "@core" is read only. Updates to Drupal core cannot be applied against a read only file system.', [
-      '@core' => $app_root . DIRECTORY_SEPARATOR . 'core',
-    ]);
-    $expected_messages[] = $this->t('Vendor filesystem at "@vendor" is read only. Updates to the site\'s code base cannot be applied against a read only file system.', [
-      '@vendor' => $app_root . DIRECTORY_SEPARATOR . 'vendor',
-    ]);
-    $messages = $readonly->run();
-    self::assertEquals($expected_messages, $messages);
-
-    // Test delete fails.
-    $messages = $readonly->run();
-    self::assertEquals($expected_messages, $messages);
-  }
-
-}
diff --git a/tests/src/Kernel/ReadinessChecker/VendorTest.php b/tests/src/Kernel/ReadinessChecker/VendorTest.php
index e7ab4c5abc41be637c4ade1bd1d1320d9c34f15b..d1a00bbb7715cd1022db40936eced0cfc350fb6c 100644
--- a/tests/src/Kernel/ReadinessChecker/VendorTest.php
+++ b/tests/src/Kernel/ReadinessChecker/VendorTest.php
@@ -5,6 +5,7 @@ namespace Drupal\Tests\automatic_updates\Kernel\ReadinessChecker;
 use Drupal\automatic_updates\ReadinessChecker\Vendor;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\KernelTests\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
 
 /**
  * Tests locating vendor folder.
@@ -28,24 +29,15 @@ class VendorTest extends KernelTestBase {
     $vendor = $this->container->get('automatic_updates.vendor');
     $this->assertEmpty($vendor->run());
 
-    $missing_vendor = $this->getMockBuilder(Vendor::class)
-      ->setConstructorArgs([
-        $this->container->get('app.root'),
-      ])
-      ->setMethods([
-        'exists',
-      ])
-      ->getMock();
-    $missing_vendor
-      ->method('exists')
-      ->withAnyParameters()
-      ->will($this->onConsecutiveCalls(
-        TRUE,
-        FALSE
-      )
-    );
-    $expected_messages = [];
-    $expected_messages[] = $this->t('The vendor folder could not be located.');
+    vfsStream::setup('root');
+    vfsStream::create([
+      'core' => [
+        'core.api.php' => 'test',
+      ],
+    ]);
+    $missing_vendor = new Vendor(vfsStream::url('root'));
+    $this->assertEquals([], $vendor->run());
+    $expected_messages = [$this->t('The vendor folder could not be located.')];
     self::assertEquals($expected_messages, $missing_vendor->run());
   }