0

I want to instantiate OrderAbstraction service which requires entity manager in constructor in a test case.

class OrderAbstraction
{
    ...

    /**
     * @var EntityManager
     */
    private $em;

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

I've tried to autowire OrderAbstractionTest and give a OrderAbstraction and entity manager as parameter.

class OrderAbstractionTest extends TestCase
{
// tried with constructor and without it
    public function testDays(){
        $order = new OrderAbstraction();
        $order->dateFrom(new \DateTime('2019-08-20 14:00'));
        $order->dateTo(new \DateTime('2019-08-28 14:00'));

        $days = $order->days();
        $this->assertEquals(8, $days);
    }
}

my service.yaml, autowire set to true

    App\Service\OrderAbstraction\:
        resource: '../src/Service/OrderAbstraction.php'
        arguments: 
            $em: '@Doctrine\ORM\EntityManager'

    App\Test\OrderAbstraction\:
        resource: '../tests/OrderAbstractionTest.php'
        arguments: 
            $em : '@Doctrine\ORM\EntityManager'

I keep getting something like this:

PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function App\Test\OrderAbstractionTest::__construct(), 0 passed in /var/www/html/autocom/bin/.phpunit/phpunit-6.5/src/Framework/TestSuite.php on line 476 and exactly 1 expected in /var/www/html/autocom/tests/OrderAbstractionTest.php:13

Would be great to know how to instantiate services like OrderAbstraction to other services, tests. Because most of the I do something like this in controllers:

$order = new OrderAbstraction($this->em);
Efkiss
  • 95
  • 3
  • 10
  • Can you show us your TestSuite.php, especially around line 476 – Dylan KAS Aug 05 '19 at 09:38
  • $constructor = $theClass->getConstructor(); if ($constructor !== null) { $parameters = $constructor->getParameters(); // TestCase() or TestCase($name) if (\count($parameters) < 2) { $test = new $className; } // TestCase($name, $data) else { try { $data = \PHPUnit\Util\Test::getProvidedData( $className, $name ); } catch (IncompleteTestError $e) Sorry can't format text – Efkiss Aug 05 '19 at 09:44
  • I also get error 1) App\Test\OrderAbstractionTest::testDays ArgumentCountError: Too few arguments to function App\Service\OrderAbstraction::__construct(), 0 passed in /var/www/html/autocom/tests/OrderAbstractionTest.php on line 13 and exactly 1 expected /var/www/html/autocom/src/Service/OrderAbstraction.php:173 /var/www/html/autocom/tests/OrderAbstractionTest.php:13 – Efkiss Aug 05 '19 at 09:52
  • Have you tried dumping $this->em when you try to instantiate OrderAbstraction to see if it's really an EM ? – Dylan KAS Aug 05 '19 at 09:53
  • I believe I can't dump in unit test, but I think $this->em is a null. My unit test doesn't receive @doctrine.orm.entity_manager from service.yaml, I'll try to work on that. But I thought It should be possible to instantiate a service without providing an entity manager. – Efkiss Aug 05 '19 at 09:58
  • You are trying to create an OrderAbstraction by passing an EM, if it's null, it's not surprising you are gonna have a problem. – Dylan KAS Aug 05 '19 at 10:00
  • Ok, should I autowire entity manager in unit test? I thought autowiring OrderAbstraction should solve this problem. – Efkiss Aug 05 '19 at 10:11

2 Answers2

3

You don't need anything in the services.yml if autowire is set to true. Replace EntityManager by EntityManagerInterface in your OrderAbstraction.php to get an instance of EntityManager being automatically injected when your service is autowired.

OrderAbstraction.php

use Doctrine\ORM\EntityManagerInterface;

class OrderAbstraction {

  /**
   * @var EntityManager
   */
  private $em;

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

EDIT

As this would work as expected in a basic controller, you can't use the constructor of your unit test class to inject your service. According to Symfony's blog :

In Symfony 4.1, tests allow fetching private services by default. In practice, tests based on WebTestCase and KernelTestCase now access to a special container via the static::$container property that allows fetching non-removed private services:

OrderAbstractionTest.php

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use App\Service\OrderAbstraction;

class OrderAbstractionTest extends WebTestCase {

  /**
   * @var OrderAbstraction
   */
  private $order;

  public function testDays() {
    self::bootKernel();
    $this->order = static::$container->get(OrderAbstraction::class);

    $this->order->dateFrom(new \DateTime('2019-08-20 14:00'));
    $this->order->dateTo(new \DateTime('2019-08-28 14:00'));

    $days = $this->order->days();
  }
}

Tested and working fine on Symfony 4.3
See this anwser for more info about Symfony 3.4 and 4.0

Reqven
  • 1,688
  • 1
  • 8
  • 13
  • Thank you for your answer. Unfortunately I get this error: PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function App\Test\OrderAbstractionTest::__construct(), 0 passed in /var/www/html/autocom/bin/.phpunit/phpunit-6.5/src/Framework/TestSuite.php on line 476 and exactly 1 expected in /var/www/html/autocom/tests/OrderAbstractionTest.php:17 – Efkiss Aug 05 '19 at 11:15
  • 1
    This solution works, you just forgot to add $container = self::$kernel->getContainer(); thank your for your help! – Efkiss Aug 06 '19 at 06:28
0

What end up working for me is php unit mock, my code now looks like this:

    public function testDays(){
        $em = $this->getMockBuilder(EntityManager::class)
        ->disableOriginalConstructor()
        ->getMock();

        $order = new OrderAbstraction($em);
        $order->dateFrom(new \DateTime('2019-08-20 14:00'));
        $order->dateTo(new \DateTime('2019-08-28 14:00'));

        $days = $order->days();
        $this->assertEquals(8, $days);
    }
Efkiss
  • 95
  • 3
  • 10