Functional tests report constants are undefined, even if the constant is set in the test or phpunit.xml I have unit and kernel tests that check for the existence of secrets (via define) in a settings.secrets.php file in my local site (Acquia Cloud convention). This file is not committed to the repo, stored in the database or exported.

When writing a functional test, I ran into an issue (which sounds similar to this one). If I tried to put define in my test file, I would just get an error and test failure saying the constant is undefined.

I came up with this, overriding prepareSettings() to write to the settings file generated for functional tests:

  /**
   * {@inheritdoc}
   */
  protected function prepareSettings() {
    parent::prepareSettings();
    $directory = DRUPAL_ROOT . '/' . $this-&gt;siteDirectory;
    copy(DRUPAL_ROOT . '/sites/example.secrets.settings.php', $directory . '/secrets.settings.php');
    $append_line = 'include_once dirname(__FILE__) . \'/secrets.settings.php\';';
    file_put_contents($directory . '/settings.php', $append_line, FILE_APPEND);
  }


The file that is copied just contains define statements with fake values.

Is this a valid approach to fixing this problem in a functional test, or did I miss something else and don't need the above code?

Update:

The code in the question only works for Functional tests as they have that method. Lower level kernel and unit tests have no access to that.

Instead, I found that you can add constants to phpunit.xml and then the tests have them defined:

    &lt;php&gt;
        &lt;const name="MYMODULE_API_URL" value="https://api.myproject.localhost"/&gt;
        &lt;const name="MYMODULE_API_KEY" value="123456789"/&gt;
        &lt;!-- Set error reporting to E_ALL. --&gt;
        &lt;ini name="error_reporting" value="32767"/&gt;


There appears to be one exception though - BrowserTests that have calls to drupalGet can still throw Use of undefined constant exception and fail the test. I can't figure that part out. I also tried using a testing profile that does not enable page or dynamic cache - same result. For now I had to comment out the drupalGet calls until I figure it out later.

However by adding it to phpunit.xml, you cannot do it in prepareSettings or you get a 'constant already defined'. So it appears that it is aware of the existing of this value.

Why do non BrowserTests not adhere to constants that are set either in code or in phpunit.xml, but any other tests do?

Update 2:

Here is the code in the test. The lines in the first loop for drupalGet will fail with the error, but curiously, the last block passes just fine:

    foreach ($permitted as $account) {
      $this-&gt;drupalLogin($account);

      foreach ($this-&gt;entity_types as $entity_type) {
        $this-&gt;drupalGet('node/' . $entity_type['nid'] . '/edit');

        if ($entity_type['response'] === 200) {
          $this-&gt;assertLinkByHref('/node/' . $entity_type['nid'] . '/update');
          $this-&gt;assertLinkByHref('/node/' . $entity_type['nid'] . '/data');
        } else {
          $this-&gt;assertNoLinkByHref('/node/' . $entity_type['nid'] . '/update');
          $this-&gt;assertNoLinkByHref('/node/' . $entity_type['nid'] . '/data');
        }

        $this-&gt;drupalGet('/node/' . $entity_type['nid'] . '/update');
        $this-&gt;assertResponse($entity_type['response']);

        $this-&gt;drupalGet('/node/' . $entity_type['nid'] . '/data');
        $this-&gt;assertResponse($entity_type['response']);
      }

      $this-&gt;drupalLogout();
    }

    // As someone without the specific permission
    $this-&gt;drupalLogin($this-&gt;staff);

    foreach ($this-&gt;entity_types as $entity_type) {
      $this-&gt;drupalGet('node/' . $entity_type['nid'] . '/edit');
      $this-&gt;assertNoLinkByHref('/node/' . $entity_type['nid'] . '/update');
      $this-&gt;assertNoLinkByHref('/node/' . $entity_type['nid'] . '/data');
      $this-&gt;drupalGet('/node/' . $entity_type['nid'] . '/update');
      $this-&gt;assertResponse(403);
      $this-&gt;drupalGet('/node/' . $entity_type['nid'] . '/data');
      $this-&gt;assertResponse(403);
    }
The code in the question only works for Functional tests as they have that method. Lower level kernel and unit tests have no access to that, and thus, they would fail.

Instead, I found that you can add constants to phpunit.xml and then the tests have them defined:

    &lt;php&gt;
        &lt;const name="MYMODULE_API_URL" value="https://api.myproject.localhost"/&gt;
        &lt;const name="MYMODULE_API_KEY" value="123456789"/&gt;
        &lt;!-- Set error reporting to E_ALL. --&gt;
        &lt;ini name="error_reporting" value="32767"/&gt;


Then you can remove the prepareSettings method from functional tests. However, there seems to be another bug (that I can't track down) where tests that use drupalGet can sometimes fail with an undefined constant error, even though it is defined in the configuration file (you can do it in code too, and will still fail).

My only guess here is that its an issue with a new process spawn not getting the defined configuration from phpunit.xml, or files are loaded out of order (test file constants aren't seen?). 

I settled on the following. I left the constants defined in phpunit.xml so my unit/kernel tests did not need them littered in the tests. For functional tests, I put prepareSettings back in with some changes:

  protected function prepareSettings() {
    parent::prepareSettings();
    $directory = DRUPAL_ROOT . '/' . $this-&gt;siteDirectory;
    copy(DRUPAL_ROOT . '/sites/functional.settings.php', $directory . '/functional.settings.php');
    $append = 'include dirname(__FILE__) . \'/functional.settings.php\';';
    file_put_contents($directory . '/settings.php', $append, FILE_APPEND | LOCK_EX);
  }


The contents of the file:

/**
 * @file
 * Functional tests settings file.
 *
 * There is a weird bug where constants are not recognized in certain cases
 * in functional tests, despite them being defined in phpunit.xml.dist.
 *
 * Functional tests should include any required constants here to avoid this
 * bug.
 *
 * @see https://drupal.stackexchange.com/questions/292713/functional-tests-report-constants-are-undefined-even-if-the-constant-is-set-in
 */

if (!defined('MYMODULE_API_URL')) {
  define('MYMODULE_API_URL', 'https://api.docker.localhost');
}

if (!defined('MYMODULE_API_key')) {
  define('MYMODULE_API_key', 'foobar');
}


Now, with the functional test uncommenting all the calls to drupalGet, all tests are passing with no errors about undefined constants.