PHPUnit test not sharing the entity manager with Symfony - php

I'm running Symfony 5.4 and PHPUnit 9.5. I have a test which extends Symfony\Bundle\FrameworkBundle\Test\WebTestCase. I create an entity in my test, then execute the code under test. However, in my app's code, the entity is not to be found. I've tried finding the entity directly in the called app code and using dd() to dump it out (ending the test early), but I always get null. Somehow my test is using a different entity manager from the app code. This is how I'm fetching the entity manager:
<?php
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class MyTest extends WebTestCase {
protected EntityManager $entity_manager;
protected function setUp(): void {
$this->entity_manager = static::getContainer()->get('doctrine')->getManager();
}
public function testShouldCreateAnEntityThatIsVisibleInTheAppCode() {
$user = new User();
$user->setFirstName('Joe');
$user->setLastName('Bloggs');
$user->setEmail('joe.bloggs#example.com');
$this->entity_manager->persist($user);
$this->entity_manager->flush();
$crawler = static::$client->request('GET', 'https://localhost/admin/show-users');
$this->assertStringContainsString('joe.bloggs#example.com', $crawler->html());
}
}
How do I get my test to use the same entity manager as the code under test?

It turned out all I needed to do was add
static::$client->disableReboot();
into setUp() like so:
protected function setUp(): void {
static::$client->disableReboot();
$this->entity_manager = static::getContainer()->get('doctrine')->getManager();
}

Related

PHPUnit: How to test Doctrine Repository method that returns void?

I have a Doctrine repository class that can be used to persist a User, I don't want to check if the entity was really persisted or not, I just want to know what to do in this situation:
It's easy to test a repository method that has a return value. But in this case, I have nothing to do, and I'd like to cover 100% of the code, without making unsafe code that can break like use #addToAssertionCount.
<?php
namespace Domain\Repository;
use DateTime;
use Domain\Entity\User;
use Domain\Repository\Interfaces\UserRepositoryInterface;
class UserRepository extends Repository implements UserRepositoryInterface
{
public function create(User $user): void
{
$user->setCreatedAt(new DateTime('now'));
$this->getEntityManager()->persist($user);
}
}
And a testing class for it:
<?php
namespace Domain\Repository;
use Doctrine\ORM\EntityManager;
use Domain\Entity\User;
use PHPUnit\Framework\TestCase;
class UserRepositoryTest extends TestCase
{
private UserRepository $sut;
public function setUp(): void
{
$entity_manager = $this->createMock(EntityManager::class);
$this->sut = new UserRepository($entity_manager);
}
public function test_assert_create(): void
{
$user = $this->createMock(User::class);
$this->sut->create($user);
// What to assert??
}
}
At this point, I don't know even what to assert, once the persist() method returns void, which I can't mock.
Focusing on 100% code coverage is not a good idea and encourages writing tests that have little to no value. What does that mean? The create method has two side effects: It changes the users creation date and persists it. You could test it like this:
final class UserRepositoryTest extends TestCase
{
/**
* #var EntityManager&MockObject
*/
private EntityManager $entityManager;
private UserRepository $sut;
public function setUp(): void
{
$this->entityManager = $this->createMock(EntityManager::class);
$this->sut = new UserRepository($this->entityManager);
}
public function test_create_should_persist_entity(): void
{
$user = new User();
$user->setCreatedAt(new DateTime('2000-01-01 12:15:30'));
// validate that persist call was made
$this->entityManager->expects(self::once())
->method('persist')
->with($user);
$this->sut->create($user);
// validate that creation date was set
self::assertEqualsWithDelta(new DateTime('now'), $user->getCreatedAt(), 3);
}
}
You could go even one step further and verify that the creation date was set before the persist call was made by using the callback constraint. But then, you're pretty much checking the implementation line by line in your test. That's how people end with tests that break all the time.
So, what to do instead? Focus on the purpose of the user repository: If you put something in, you should be able to get it out. This, however, requires that you use an actual entity manager. But you wrote, that you don't want to check that the entity was actually persisted. In that case, I would rather write no tests than the example I gave above.

symfony + phpunit - how to test UserFactory with PasswordEncoder dependencies

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)

Fixture object reference incorrect in functional test

A functional test class relies on an object reference created in a fixture. The reference's id, however, is not identical to the object's id property as returned by the entity manager. Below is a test that demonstrates this problem.
Notes:
The error is the same when using $this->setReference(...) as when
using the public const ... and $this->addReference(...).
The object reference used in the test appears to be the next
available id for nonprofit entities.
The test class was created after the error was observed in a more general test class.
The error is the same whether or not the fixtures are loaded before
running the test class.
The application uses Symfony 5.1.2 with all dependencies updated.
Test class:
namespace App\Tests\Controller;
use Liip\TestFixturesBundle\Test\FixturesTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ReferenceTest extends WebTestCase
{
use FixturesTrait;
public function setup(): void {
$this->client = $this->createClient();
$this->fixtures = $this->loadFixtures([
'App\DataFixtures\Test\OptionsFixture',
'App\DataFixtures\Test\NonprofitFixture',
'App\DataFixtures\Test\OpportunityFixture',
'App\DataFixtures\Test\UserFixture',
])
->getReferenceRepository();
$this->client->followRedirects();
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')
->getManager('test');
}
public function testNonprofitReference() {
$npo = $this->entityManager->getRepository(\App\Entity\Nonprofit::class)
->findOneBy(['orgname' => 'Marmot Fund']);
$nId = $npo->getId();
$id = $this->fixtures->getReference('npo')->getId();
$this->assertEquals($nId, $id, 'Reference incorrect');
}
}
Test result:
Reference incorrect
Failed asserting that 4 matches expected 1.
NonprofitFixture (other fixtures may not be relevant):
namespace App\DataFixtures\Test;
use App\Entity\Nonprofit;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
class NonprofitFixture extends AbstractFixture implements OrderedFixtureInterface, ORMFixtureInterface
{
public const NPO_REFERENCE = 'npo';
public function load(ObjectManager $manager) {
$npo = new Nonprofit();
$npo->setOrgname('Marmot Fund');
$npo->setEin('123456789');
$npo->setActive(false);
// $this->setReference('npo', $npo);
$this->addReference(self::NPO_REFERENCE, $npo);
$npo1 = new Nonprofit();
$npo1->setOrgname('Turkey Fund');
$npo1->setEin('321654978');
$npo1->setActive(true);
$npo1->setWebsite('http://turkeysRUs.bogus.info');
$npo3 = new Nonprofit();
$npo3->setOrgname('Talk Trash Fund');
$npo3->setEin('978654321');
$npo3->setActive(true);
$npo3->setWebsite('http://ttrash.bogus.info');
$staff = $this->getReference(UserFixture::STAFF_REFERENCE);
$npo->setStaff($staff);
$opp = $this->getReference(OpportunityFixture::OPP_REFERENCE);
$opp1 = $this->getReference(OpportunityFixture::OPP1_REFERENCE);
$npo1->addOpportunity($opp);
$npo3->addOpportunity($opp1);
$manager->persist($npo);
$manager->persist($npo1);
$manager->persist($npo3);
$manager->flush();
}
public function getOrder() {
return 5; // the order in which fixtures will be loaded
}
}
framework.yaml excerpt:
liip_test_fixtures:
keep_database_and_schema: true
cache_db:
sqlite: liip_test_fixtures.services_database_backup.sqlite
dama_doctrine_test_bundle.yaml:
dama_doctrine_test:
enable_static_connection: true
enable_static_meta_data_cache: true
enable_static_query_cache: true
csv export from app.db:
"id","orgName"
"1","Marmot Fund"
"2","Turkey Fund"
"3","Talk Trash Fund"
The answer, such as it is, is that references have no place in a functional test. Their use is really a shortcut for clicking on links or taking some other action. A better test is to use the crawler to mimic the action.

Factory helper not working within PHPUnit setup method

Writing some unit tests, and I want to have an object created before the tests in the class are done. So I set up the setUpBeforeClass() method:
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Location;
class UserTests extends TestCase {
const FAKEID = 9999999;
public static function setUpBeforeClass() : void {
parent::setUpBeforeClass();
factory(Location::class)->make(["id" => self::FAKEID])->save();
}
}
But when I try running this, I get this error:
InvalidArgumentException: Unable to locate factory with name [default] [App\Location].
But the factory class is set up properly. In fact, if I move this same line down to one of my test functions it works perfectly.
public function testCreateUser() {
factory(Location::class)->make(["id" => self::FAKEID])->save();
// do other stuff...
}
The only thing that sticks out to me as different about setUpBeforeClass() is that it's a static method, but I don't know why that would prevent the factory class from working.
Laravel does a lot of setting up in the setUp() method in the TestCase class. The setUpBeforeClass() method is called before that, that's why your factory is not loaded yet.
The Laravel's TestCase class setup method (see class):
/**
* Setup the test environment.
*
* #return void
*/
protected function setUp()
{
if (! $this->app) {
$this->refreshApplication();
}
$this->setUpTraits();
foreach ($this->afterApplicationCreatedCallbacks as $callback) {
call_user_func($callback);
}
Facade::clearResolvedInstances();
Model::setEventDispatcher($this->app['events']);
$this->setUpHasRun = true;
}
Change your code to use setUp instead:
protected static function setUp() : void
{
parent::setUp();
factory( Location::class )->make( ["id" => self::FAKEID] )->save();
}

Laravel 5.7: I inject bunch on classes into constructor but League\Csv\Reader is the only one that doesn't work

I have some artisan commands to perform some CLI logic.
class SyncFooCommand extends AbstractBaseSyncCommand
class SyncBarCommand extends AbstractBaseSyncCommand
class SyncBazCommand extends AbstractBaseSyncCommand
Each artisan command extends abstract class AbstractBaseSyncCommand extends Command implements SyncInterface.
Thanks to that inside the abstract parent class I can put some shared logic.
I inject Carbon or FileSystem and both work in child classes like a charm.
<?php
namespace App\Console\Commands\Sync;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\Factory as Filesystem;
use League\Csv\Reader;
abstract class AbstractBaseSyncCommand extends Command implements SyncInterface
{
protected $carbon;
protected $fileSystem;
protected $reader;
public function __construct(
Carbon $carbon,
FileSystem $fileSystem,
Reader $reader
) {
parent::__construct();
$this->carbon = $carbon; // works like a charm
$this->fileSystem = $fileSystem; // works like a charm
$this->reader = $reader; // fails, why?
}
}
In SyncWhateverCommand I can easily call & use $this->carbon or $this->fileSystem but as soon as it hits the $this->reader I get:
Illuminate\Contracts\Container\BindingResolutionException : Target [League\Csv\Reader] is not instantiable while building [App\Console\Commands\Sync\SyncFooCommand].
at /home/vagrant/code/foo/vendor/laravel/framework/src/Illuminate/Container/Container.php:945
941| } else {
942| $message = "Target [$concrete] is not instantiable.";
943| }
944|
> 945| throw new BindingResolutionException($message);
946| }
947|
948| /**
949| * Throw an exception for an unresolvable primitive.
What's wrong? The installation was easy and didn't require to do any bindings. What am I missing?
To sum up:
$csv = $this->reader->createFromPath($path, 'r'); // fails, but I want to use it this way
$csv = Reader::createFromPath($path, 'r'); // works but I don't want facades because they look ugly
Maybe your app should know how to retrieve it? Like
$this->app->bind('SomeAbstraction', function ($app) {
return new Implementation();
});

Categories