1

Let's say I have this class that I was spec-ing (following BDD approach)

class Logger
{
    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function logMessageAsRead(Message $message)
    {
        $log = new LoggedMessage($message);
        $this->em->persist($message);
    }
}

and LoggedMessage is defined as follows

class LoggedMessage
{
    private $date;

    private $message;

    public function __construct(Message $message)
    {
        $this->date = new \DateTime();
        $this->message = $message;
    }
}

Sometimes my spec example fails due to discrepancy from Message date instantiated in spec and the one in the Logger class.

class LoggerSpec
{
    public function it_logs_a_message(Message $message, EntityManager $em)
    {
        $log = new LoggedMessage($message);
        $em->persist($log)->shouldBeCalled(1);

        $this->logMessageAsRead($message);
    }
}

Question number one: Do I have a smell in my code, so do I need to create a collaborator (ie.: a factory) and inject it into Logger in order to create a new LoggedMessage?

Question number two: If is not necessary to inject a new collaborator, how can I be sure that my spec works every single time and not fail in a random fashion due to date time discrepancy?

DonCallisto
  • 29,419
  • 9
  • 72
  • 100

1 Answers1

0
  1. An injected factory of LoggedMessages would be a good solution, especially if you don't want to close the constructor of LoggedMessage against modifications. In general, it's good to separate the concern of creating an object from using it.

  2. The simplest solution would be to check for a specific type instead of a concrete instance:

    $em->persist(Argument::type(LoggedMessage::class))->shouldBeCalled(1);

If you want your expectation to be more specific, you can use Argument::which or Argument::that:

$em->persist(Argument::which('getMessage', $message))->shouldBeCalled(1);
$em->persist(Argument::that(function($arg) {
    return $arg->getDate() instanceof \DateTime;
}))->shouldBeCalled(1);
Bartosz Zasada
  • 3,762
  • 2
  • 19
  • 25
  • Thank you for your answer. All your consideration are same as mine so I guess that a factory is the only way to do this. However second point is not a good one: it's too little tighter for this kind of example (phpspec example to be clear) – DonCallisto Nov 07 '16 at 09:11