I'm trying to do a unit test for a signup method and im following this guide. I'm fairly new to unit testing.
i keep getting
1) App\Tests\Controller\SignUpControllerTest::testSignUp Error: Cannot
instantiate interface
Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface
/Applications/MAMP/htdocs/my_project/tests/Controller/SignUpControllerTest.php:19
I just don't think im doing this unit test right. Here is what i have. I'm not sure on what im doing. All i want to do is test the signup method.
UserController.php
public function signup(Request $request, UserPasswordEncoderInterface $passwordEncoder )
{
$user = new User();
$entityManager = $this->getDoctrine()->getManager();
$user->setEmail($request->get('email'));
$user->setPlainPassword($request->get('password'));
$user->setUsername($request->get('username'));
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$entityManager->persist($user);
$entityManager->flush();
return $this->redirectToRoute('login');
}
SignUpControllerTest.php
namespace App\Tests\Controller;
use App\Entity\Product;
use App\Controller\UserController;
use App\Entity\User;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\ObjectRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class SignUpControllerTest extends WebTestCase
{
public function testSignUp()
{
$passwordEncoder = new UserPasswordEncoderInterface();
$user = new User();
$user->setEmail('janedoe123#aol.com');
$user->setPlainPassword('owlhunter');
$user->setUsername('BarnMan');
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$userRepository = $this->createMock(ObjectRepository::class);
$userRepository->expects($this->any())
->method('find')
->willReturn($user);
$objectManager = $this->createMock(ObjectManager::class);
// use getMock() on PHPUnit 5.3 or below
// $objectManager = $this->getMock(ObjectManager::class);
$objectManager->expects($this->any())
->method('getRepository')
->willReturn($userRepository);
$userController = new UserController($objectManager);
$this->assertEquals(2100, $userController->signupTest());
}
}
The error is very clear. In the first line of your testSignUp method, you are creating an instance out of an interface, which cannot be done in PHP.
To create a usable object out of an interface in unit testing, create a mock object of it. Read PHP unit docs for that.
WebTestCase :
Application tests are PHP files that typically live in the tests/Controller/ directory of your application it tell Symfony that we need the tools necessary to talk to our application as if we were doing so via HTTP.
KernelTest
use for to fetch a service from the dependency injection container it help you creating and booting the kernel in your tests
that's why I recommend you to use KernelTestCase
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class SignUpControllerTest extends KernelTestCase
{
private $passwordEncoder;
protected function testSignUp(): void
{
self::bootKernel();
$this->passwordEncoder= self::getContainer()->get('Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface');
}
public function addShoes3()
{
$passwordEncoder = $this->passwordEncoder;
//continue
}
}
Related
As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.
Hello i have problem with my test.
I'm trying to test UserFactory which is creating UserObject by UserDto data.
I dont know how to test it because factory need PasswordEncoder in dependencies.
use App\Entity\User;
use App\Service\Factory\UserFactory;
use PHPUnit\Framework\MockObject\MockObject as MockObject;
use PHPUnit\Framework\TestCase;
class UserFactoryTest extends TestCase
{
/**
* #covers UserFactory
*/
public function testShouldCreateUserObjectFromUserDto()
{
//Given
/**
* #var UserDto | MockObject
*/
$userDto = $this->getMockBuilder(UserDto::class);
//When
$userFactory = new UserFactory(/* PASSWORD ENCODER */);
$user = $userFactory->create($userDto);
//Then
$this->assertInstanceOf(User::class, $user);
}
}
namespace App\Service\Factory;
use App\Entity\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserFactory
{
/**
* #var UserPasswordEncoderInterface
*/
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function create(UserDto $dto)
{
$user = new User();
$user->setPassword($this->encoder->encodePassword($dto->plainPassword));
/**
* CODE..
*/
return $user;
}
}
Is this right? how can i test this factory which have dependencies?
i cant user __construct in my TestClass
(Hint: apparently you're using the UserPasswordEncoderInterface wrong, because it's not a PasswordEncoderInterface - the former additionally expects the User as a param, while the latter does not, which I just learned today, you might want to fix that)
In general, you have to provide all dependencies for the class to be tested. There are different approaches, essentially: Implementing a reduced version of the interface (if it's an interface), actually finding the dependency and instantiate it or use a mock and tell it what will happen to it. The latter can be done if you generally know what happens with the dependency (non-blackbox testing).
So, you could just mock the interface with some well defined behavior:
//$upe = $this->getMockBuilder(\Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface::class)->getMock();
$upe = $this->getMockBuilder(\Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface::class)->getMock();
$upe->expects($this->any())
->method('encodePassword')
// omit following line, if you don't want to check for param
->with($this->equalTo('plainPassword'))
->willReturn('encodedPassword');
and then provide that mock to your userfactory:
$userfactory = new UserFactory($upe);
this is somewhat dependant on the implementation of UserFactory though, expecting it to essentially be called in the specified way and which will return the same string always. (You can go deeper into mocking and/or implement the interface yourself to have more control)
How can I mock a service in a functional test use-case where a "request"(form/submit) is being made. After I make the request all the changes and mocking I made to the container are lost.
I am using Symfony 4 or 5. The code posted here can be also found here: https://github.com/klodoma/symfony-demo
I have the following scenario:
SomeActions service is injected into the controller constructor
in the functional unit-tests I try to mock the SomeActions functions in order to check that they are executed(it sends an email or something similar)
I mock the service and overwrite it in the unit-tests:
$container->set('App\Model\SomeActions', $someActions);
Now in the tests I do a $client->submit($form); which I know that it terminates the kernel.
My question is: HOW can I inject my mocked $someActions in the container after $client->submit($form);
Below is a sample code I added to the symfony demo app
https://github.com/symfony/demo
in services.yaml
App\Model\SomeActions:
public: true
SomeController.php
<?php
namespace App\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to send some emails
*
* #Route("/some")
*/
class SomeController extends AbstractController
{
private $someActions;
public function __construct(SomeActions $someActions)
{
//just dump the injected class name
var_dump(get_class($someActions));
$this->someActions = $someActions;
}
/**
* #Route("/action", methods="GET|POST", name="some_action")
* #param Request $request
* #return Response
*/
public function someAction(Request $request): Response
{
$this->someActions->doSomething();
if ($request->get('send')) {
$this->someActions->sendEmail();
}
return $this->render('default/someAction.html.twig', [
]);
}
}
SomeActions
<?php
namespace App\Model;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class SomeActions
{
private $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function doSomething()
{
echo 'doSomething';
}
public function sendEmail()
{
echo 'sendEmail';
$email = (new Email())
->from('hello#example.com')
->to('you#example.com')
->subject('Time for Symfony Mailer!')
->text('Sending emails is fun again!')
->html('<p>See Twig integration for better HTML integration!</p>');
$this->mailer->send($email);
}
}
SomeControllerTest.php
<?php
namespace App\Tests\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class SomeControllerTest extends WebTestCase
{
public function testSomeAction()
{
$client = static::createClient();
// gets the special container that allows fetching private services
$container = self::$container;
$someActions = $this->getMockBuilder(SomeActions::class)
->disableOriginalConstructor()
->getMock();
//expect that sendEmail will be called
$someActions->expects($this->once())
->method('sendEmail');
//overwrite the default service: class: Mock_SomeActions_e68f817a
$container->set('App\Model\SomeActions', $someActions);
$crawler = $client->request('GET', '/en/some/action');
//submit the form
$form = $crawler->selectButton('submit')->form();
$client->submit($form);
//after submit the default class injected in the controller is "App\Model\SomeActions" and not the mocked service
$response = $client->getResponse();
$this->assertResponseIsSuccessful($response);
}
}
The solution is to disable the kernel reboot:
$client->disableReboot();
It makes sense if ones digs deep enough to understand what's going on under the hood;
I am still not sure if there isn't a more straight forward answer.
public function testSomeAction()
{
$client = static::createClient();
$client->disableReboot();
...
I created the console command but during the execution I get the following error:
In ControllerTrait.php line 338:
Call to a member function has() on null
DatabaseHelper.php
<?php
namespace App\Controller;
use App\Entity\Currency;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Controller\CurrencyComparison;
class DatabaseHelper extends AbstractController
{
public function insertValues()
{
$curFromAPI = new CurrencyComparison();
$curFromAPI = $curFromAPI->compareCurrencies();
$date = new \DateTime('#'.strtotime('now'));
$entityManager = $this->getDoctrine()->getManager();
$currency = new Currency();
$currency->setName('USD');
$currency->setAsk($curFromAPI->getUSD());
$currency->setLastUpdated($date);
$entityManager->persist($currency);
$entityManager->flush();
}
}
CurrencyComparison.php
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use App\Service\DatabaseHelper;
class CurrencyComparison extends Command
{
private $databaseHelper;
public function __construct(DatabaseHelper $databaseHelper){
$this->$databaseHelper = $databaseHelper;
parent::__construct();
}
protected function configure()
{
$this
->setName('app:currency-comparison')
->setDescription('Inserts currencies into the database.')
->setHelp('Compares currencies from APIs and inserts the lower rates into the database.')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->databaseHelper->insertValues();
$output->writeln([
'Currencies are being inserted into the database',
'============',
'',
]);
$output->writeln('Done.');
}
}
When debugging, noticed that I'm getting this error after the following line in DatabaseHelper.php:
$entityManager = $this->getDoctrine()->getManager();
What should I change or should be looking for?
Thanks.
--UPDATE--
I created a Service and tried to inject EntityManager as construct.
App/Service/DatabaseHelper.php
<?php
namespace App\Service;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DatabaseHelper extends AbstractController
{
private $entityManager;
public function __construct(EntityManager $entityManager){
$this->entityManager = $entityManager;
}
public function insertValues()
{
$curFromAPI = new CurrencyComparison();
$curFromAPI = $curFromAPI->compareCurrencies();
$date = new \DateTime('#'.strtotime('now'));
$this->entityManager = $this->getDoctrine()->getManager();
return dd($curFromAPI);
$currency = new Currency();
$currency->setName('USD');
$currency->setAsk($curFromAPI->getUSD());
$currency->setLastUpdated($date);
$entityManager->persist($currency);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
}
}
and updated Command\CurrencyComparison.php too. But when I'm trying to call execute function on CurrencyComparison.php, I cannot reach its' $this->databaseHelper->insertValues(); function. Would you mind to give any suggestions?
--UPDATE & Solution--
Removed extends AbstractController from class DatabaseHelper.
Changed __construct of DatabaseHelper and injected as follows: (EntityManagerInterface $entityManager)
removed the following line from insertValues function in DatabaseHelper.php:
$this->entityManager = $this->getDoctrine()->getManager(); and just used persist and flush functions as like above.
As your command has a dependency on DatabaseHelper that has a dependency on the Entity Manager, create the DatabaseHelper class as a service, with the Entity manager injected in via its constructor (autowire it, or specify in its service definition).
then, as per the docs here you can inject your DatabaseHelper service into the command via the constructor as it'll it be autowired by default.
I would also consider not extending the AbstractController as your DatabaseHelper is very tight in scope and wont need the entire container (assuming its only what youve posted). Just inject what you need.
I've been trying to test a model in a Symfony2 project, but I don't know how to get the entity manager to save and retrive records.
Can anyone point me to the right docs for this?
In order to test your models, you can use setUp() method. link to docs
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class MyModelTest extends WebTestCase
{
/**
* #var EntityManager
*/
private $_em;
protected function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$this->_em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
$this->_em->beginTransaction();
}
/**
* Rollback changes.
*/
public function tearDown()
{
$this->_em->rollback();
}
public function testSomething()
{
$user = $this->_em->getRepository('MyAppMyBundle:MyModel')->find(1);
}
Hope this helps you
Symfony2 models are expected to be domain objects that represent domain models in the code.
domain objects should be defined purely to implement the business
behavior of the corresponding domain concept, rather than be defined
by the requirements of a more specific technology framework. -- Domain-driven design - Wikipedia, the free encyclopedia
Domain objects (and its tests) should not depend on Symfony2 APIs and Doctrine APIs except if you really want to test themselves.
Writing Symfony2 unit tests is no different than writing standard PHPUnit unit tests. -- Symfony - Testing
You can test business logic (processes, rules, behaviors, etc.) represented in domain objects with PHPUnit (or Behat) and usually test doubles.
namespace Ibw\JobeetBundle\Tests\Repository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Input\ArrayInput;
use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;
class CategoryRepositoryTest extends WebTestCase
{
private $em;
private $application;
public function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->application = new Application(static::$kernel);
// drop the database
$command = new DropDatabaseDoctrineCommand();
$this->application->add($command);
$input = new ArrayInput(array(
'command' => 'doctrine:database:drop',
'--force' => true
));
$command->run($input, new NullOutput());
// we have to close the connection after dropping the database so we don't get "No database selected" error
$connection = $this->application->getKernel()->getContainer()->get('doctrine')->getConnection();
if ($connection->isConnected()) {
$connection->close();
}
// create the database
$command = new CreateDatabaseDoctrineCommand();
$this->application->add($command);
$input = new ArrayInput(array(
'command' => 'doctrine:database:create',
));
$command->run($input, new NullOutput());
// create schema
$command = new CreateSchemaDoctrineCommand();
$this->application->add($command);
$input = new ArrayInput(array(
'command' => 'doctrine:schema:create',
));
$command->run($input, new NullOutput());
// get the Entity Manager
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager();
// load fixtures
$client = static::createClient();
$loader = new \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader($client->getContainer());
$loader->loadFromDirectory(static::$kernel->locateResource('#IbwJobeetBundle/DataFixtures/ORM'));
$purger = new \Doctrine\Common\DataFixtures\Purger\ORMPurger($this->em);
$executor = new \Doctrine\Common\DataFixtures\Executor\ORMExecutor($this->em, $purger);
$executor->execute($loader->getFixtures());
}
public function testFunction()
{
// here you test save any object or test insert any object
}
protected function tearDown()
{
parent::tearDown();
$this->em->close();
}
}
like in this Link : Jobeet Unit Test Tutorial
explain how to Test Entity and Entity Repository