diff --git a/tests/src/Unit/Integration/ContributedPluginDiscoveryTest.php b/tests/src/Unit/Integration/ContributedPluginDiscoveryTest.php
new file mode 100644
index 0000000..803318e
--- /dev/null
+++ b/tests/src/Unit/Integration/ContributedPluginDiscoveryTest.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\Tests\rules\Unit\Integration;
+
+/**
+ * Ensures that Rules plugins defined by contributed (and test) modules may
+ * be found by RulesIntegrationTestBase.
+ *
+ * @coversDefaultClass \Drupal\rules_test\Plugin\Condition\TestConditionTrue
+ * @group RulesCondition
+ */
+class ContributedPluginDiscoveryTest extends RulesEntityIntegrationTestBase {
+
+  /**
+   * The condition to be tested.
+   *
+   * @var \Drupal\rules\Core\RulesConditionInterface
+   */
+  protected $condition;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->enableModule('rules_test');
+    $this->condition = $this->conditionManager->createInstance('rules_test_true');
+  }
+
+  /**
+   * Tests evaluating a condition provided by the 'rules_test' module.
+   *
+   * We're trying to ensure that plugins from contributed modules are found in
+   * the namespace established by the RulesIntegrationTestBase::enableModule().
+   *
+   * @covers ::evaluate
+   */
+  public function testPluginDiscovery() {
+    $this->assertTrue($this->condition->evaluate());
+  }
+
+}
diff --git a/tests/src/Unit/Integration/RulesIntegrationTestBase.php b/tests/src/Unit/Integration/RulesIntegrationTestBase.php
index a28a2ec..499cea0 100644
--- a/tests/src/Unit/Integration/RulesIntegrationTestBase.php
+++ b/tests/src/Unit/Integration/RulesIntegrationTestBase.php
@@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\Discovery\RecursiveExtensionFilterIterator;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Path\AliasManagerInterface;
 use Drupal\Core\Plugin\Context\LazyContextRepository;
@@ -239,10 +240,10 @@ abstract class RulesIntegrationTestBase extends UnitTestCase {
   /**
    * Fakes the enabling of a module and adds its namespace for plugin loading.
    *
-   * Default behaviour works fine for core modules.
+   * This method allows plugins provided by a module to be discoverable.
    *
    * @param string $name
-   *   The name of the module that's gonna be enabled.
+   *   The name of the module that's going to be enabled.
    * @param array $namespaces
    *   Map of the association between module's namespaces and filesystem paths.
    */
@@ -250,7 +251,7 @@ abstract class RulesIntegrationTestBase extends UnitTestCase {
     $this->enabledModules[$name] = TRUE;
 
     if (empty($namespaces)) {
-      $namespaces = ['Drupal\\' . $name => $this->root . '/core/modules/' . $name . '/src'];
+      $namespaces = ['Drupal\\' . $name => $this->root . '/' . $this->constructModulePath($name) . '/src'];
     }
     foreach ($namespaces as $namespace => $path) {
       $this->namespaces[$namespace] = $path;
@@ -258,6 +259,53 @@ abstract class RulesIntegrationTestBase extends UnitTestCase {
   }
 
   /**
+   * Determines the path to a module's class files.
+   *
+   * Core modules and contributed modules are located in different places, and
+   * the testbot (DrupalCI) does not use same directory structure as most live
+   * Drupal sites, so we must discover the path instead of hardwiring it.
+   *
+   * This method discovers modules the same way as Drupal core, so it should
+   * work for core and contributed modules in all environments.
+   *
+   * @see \Drupal\Core\Extension\ExtensionDiscovery
+   */
+  protected function constructModulePath($module) {
+    // Use Unix paths regardless of platform, skip dot directories, follow
+    // symlinks (to allow extensions to be linked from elsewhere), and return
+    // the RecursiveDirectoryIterator instance to have access to getSubPath(),
+    // since SplFileInfo does not support relative paths.
+    $flags = \FilesystemIterator::UNIX_PATHS;
+    $flags |= \FilesystemIterator::SKIP_DOTS;
+    $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
+    $flags |= \FilesystemIterator::CURRENT_AS_SELF;
+    $directory_iterator = new \RecursiveDirectoryIterator($this->root, $flags);
+
+    // Filter the recursive scan to discover extensions only.
+    // Important: Without a RecursiveFilterIterator, RecursiveDirectoryIterator
+    // would recurse into the entire filesystem directory tree without any kind
+    // of limitations.
+    $filter = new RecursiveExtensionFilterIterator($directory_iterator);
+    // Ensure we find testing modules too!
+    $filter->acceptTests(TRUE);
+
+    // The actual recursive filesystem scan is only invoked by instantiating the
+    // RecursiveIteratorIterator.
+    $iterator = new \RecursiveIteratorIterator($filter,
+      \RecursiveIteratorIterator::LEAVES_ONLY,
+      // Suppress filesystem errors in case a directory cannot be accessed.
+      \RecursiveIteratorIterator::CATCH_GET_CHILD
+
+    );
+
+    $info_files = new \RegexIterator($iterator, "/^$module.info.yml$/");
+    foreach ($info_files as $file) {
+      // There should only be one match.
+      return $file->getSubPath();
+    }
+  }
+
+  /**
    * Returns a typed data object.
    *
    * This helper for quick creation of typed data objects.
