How to retrieve the environment in a Codeception Helper

Why should I care about the environment inside a helper?

Depending on how you’re testing your application, it can be vital to be able to determine which environment is currently running. For one of my projects, we have separate Codeception environments set up so that our acceptance tests can run against our different environments: local dev, QC, and stage.

It’s pretty easy to determine this inside individual Cests by injecting a \Codeception\Scenario parameter into your test, like this:


public function environmentAwareTest(\AcceptanceTester $I, \Codeception\Scenario $scenario)
{
    if ($scenario->current('env') === 'stage') {
        // Do things that only apply to the stage environment
    }
}

This is great for individual tests, but what if you need environment information inside Helpers, or just don’t want to deal with having to inject the $scenario every time you need it?

Getting the environment within the Acceptance Helper

Much like individual Cests, it’s possible to define a _before method inside helpers that will run before every test. This method takes a \Codeception\TestInterface parameter, which includes the exact information we need.

Here’s an example of how your Acceptance Helper can be aware of the environment:


namespace Helper;

class Acceptance extends \Codeception\Module
{
    private $environment;

    public function _before(\Codeception\TestInterface $test)
    {
        $this->environment = $test->getMetadata()->getCurrent('env');
    }

    public function getEnvironment()
    {
        return $this->environment;
    }
}

Since Helper methods are available in the AcceptanceTester, the conditional in the first example can now be rewritten like this:


public function environmentAwareTest(\AcceptanceTester $I)
{
    if ($I->getEnvironment() === 'stage') {
        // Do things that only apply to the stage environment
    }
}

Tweaks for better readability

To make things a little easier to read, we could add a couple of additional methods to the Helper:


namespace Helper;

class Acceptance extends \Codeception\Module
{
    private $environment;

    public function _before(\Codeception\TestInterface $test)
    {
        $this->environment = $test->getMetadata()->getCurrent('env');
    }

    public function getEnvironment()
    {
        return $this->environment;
    }

    public function amInQC()
    {
        return $this->getEnvironment() === 'qc';
    }

    public function amInStage()
    {
        return $this->getEnvironment() === 'stage';
    }

    public function amInLocalDev()
    {
        return $this->getEnvironment() === 'local';
    }
}

And then our example would become:


public function environmentAwareTest(\AcceptanceTester $I)
{
    if ($I->amInStage()) {
        // Do things that only apply to the stage environment
    }
}

Use them within your AcceptanceTester too

All of the above is pretty nice for making tests a little easier to read, but the actual reason I needed this approach in the first place is because I needed to know the environment from inside the AcceptanceTester itself. In my case, I was writing a login helper that needed to retrieve environment-specific credentials.

If we were depending on \Codeception\Scenario, we would need to pass it as a parameter, which just felt kind of gross. Instead, I can now write something like this in the Helper:


namespace Helper;

class Acceptance extends \Codeception\Module
{
    private $environment;

    public function _before(\Codeception\TestInterface $test)
    {
        $this->environment = $test->getMetadata()->getCurrent('env');
    }

    public function getEnvironment()
    {
        return $this->environment;
    }

    public function getUserData($key)
    {
        $data = json_decode(file_get_contents(__DIR__ . '/users.json'), true);

        return $data[$this->getEnvironment()][$key];
    }
}

Something like this in the Tester:


class AcceptanceTester extends \Codeception\Actor
{
    use _generated\AcceptanceTesterActions;

    public function logInWithUsernameAndPassword($username, $password)
    {
       // Login code
    }

    public function loginAs($key)
    {
        $userData = $I->getUserData($key);
        $I->logInWithUsernameAndPassword($userData['username'], $userData['password']);
    }
}

And finally, in the tests themselves, something like this:


public function testRequiringLogin(\AcceptanceTester $I)
{
    $I->loginAs('admin');
    // Test admin functionality
}

The relevant part of users.json looks something like this:


{
  "qc": {
    "admin": {
      "username": "qc-admin-username",
      "password": "QCPassword"
    }
  },
  "stage": {
    "admin": {
      "username": "stage-admin-username",
      "password": "STAGEPassword"
    }
  },
  "local": {
    "admin": {
      "username": "local-admin-username",
      "password": "LOCALPassword"
    }
  }
}

Have fun!

If you happened to stumble across this article, I hope you found what you were looking for. I would love to hear if this approach ended up working for you or if you ran into any problems down in the comments.

Leave a Comment