deefour
deefour

Reputation: 35360

Testing Use of Class Optionally Included in Project

I have a class that goes something like the following:

class Foo {

  /**
   * @var array|Bar
   */
  protected $source;

  public function __construct($source = []) {
    if (class_exists(Bar::class)) {
      $source = new Bar($source);
    }

    $this->source = $source;
  }

  public function getSource() {
    return $this->source;
  }

  // ...

}

Bar comes from a separate PHP package, an optional dependency listed in suggest section of composer.json.

I want to write two separate expectations in phpspec for getSource()

function it_provides_array_source_by_default() {
  $this->getSource()->shouldBeArray();
}

function it_provides_bar_instance_when_available() {
  $this->getSource()->shouldReturnAnInstanceOf(Bar::class);
}

I can't test the shouldBeArray() if my require-dev for the package includes the dependency containing Bar.

I can't mock Bar like:

function it_provides_bar_instance_when_available(Bar $bar) {
  $this->getSource()->shouldReturnAnInstanceOf(Bar::class);
}

because I get a phpspec error:

[PhpSpec\Exception\Locator\ResourceCreationException] Can not find appropriate suite scope for class Bar.

What is the best-practice method for testing the return value being an optional class like this?

Upvotes: 0

Views: 54

Answers (1)

deceze
deceze

Reputation: 522016

Move the logic that selects the type of $source into an external factory function.

class Foo {

  protected $source;

  public function __construct($source = []) {
    $this->source = $source;
  }

}

class FooFactory {

  public get() {
    $source = [];
    if (class_exists(Bar::class)) {
      $source = new Bar($source);
    }

    return new Foo($source);
  }

}

This allows you to explicitly test Foo both ways and decouples the implementation by moving everything to a dependency injected model.

Upvotes: 1

Related Questions