Sorry my question is very long.
How to mock entityManager in a service test extending KernelTestCase ?
Now, the explanation and my tests...
I am using Symfony3.2. My Application is standard. I have some Controller and I use WebTestCase to test them.
Generaly, my Controller verify parameters, call a service/a manager, handle some variables and push them to view and my test are pretty simple in test extending WebTestCase.
/**
* Test New Game Action
*/
public function testFooAction(){
//We mock the Service
$fooService = $this
->getMockBuilder(GameService::class)
->disableOriginalConstructor()
->getMock();
$fooService->expects(self::once())
->method('barMethod')
->willReturn($result);
//We create the client
$client = static::createClient();
$container = $client->getContainer();
//I put my mock here
$container->set('app.game-service', $fooService);
//I launch the request
$client->request('GET', '/foo');
//I handle the response
$response = $client->getResponse();
//I do some tests like this one
self::assertEquals(200, $response->getStatusCode());
}
As you can see, I do not call EntityManger, because I use Services and these lines to put my Services's Mock
//We create the client
$client = static::createClient();
$container = $client->getContainer();
//I put my mock here
$container->set('app.game-service', $fooService);
I have a problem to mock Entity Manager in my services. My Controller is tested but not My Service.
Here is the constructor initialized a simple protected property entityManager. The problem is this protected as you will see further...
/**
* FooService constructor.
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
To Test my Service, here is my initial code :
<?php
class FooServiceTest extends KernelTestCase
{
/**
* Foo Service.
*
* #var FooService
*/
private $fooService;
/**
* Prepares the environment before running each test.
*/
protected function setUp()
{
parent::setUp();
self::bootKernel();
$this->fooService = static::$kernel->getContainer()
->get('app.foo-service') //HERE IS HOW I HANDLE MY SERVICE TO TEST IT
;
}
testStart Works perfectly, there is some database interaction.
I know that I can rollback data during test. But I want to Mock entityManager to verify the call of commit method.
I try this to mock entity manager in my setUp :
protected function setUp()
{
parent::setUp();
$entityManager = $this
->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$entityManager
->expects(once()) // I WANT EXACTLY ONE CALL TO COMMIT METHOD
->method('commit')
->willReturn(null);
self::bootKernel();
$container = static::$kernel->getContainer();
$container->set('doctrine.orm.entity_manager', $entityManager); // THIS LINE DOES NOTHING <=======
$this->gameService = static::$kernel->getContainer()
->get('app.game-service')
;
Thes code doesn't work. Mock is not in place. I still have the true entityManager. I think it is because the container is already closed. Do it is nut usefull to set
Like a Barbarian, I change the entityManager property to public
And I do that :
protected function setUp()
{
parent::setUp();
$entityManagerMock = $this
->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
$entityManagerMock
->expects(once()) // I WANT EXACTLY ONE CALL TO commit METHOD
->method('commit')
->willReturn(null);
self::bootKernel();
$this->gameService = static::$kernel->getContainer()
->get('app.game-service')
;
$this->gameService->entityManager = entityManagerMock;
It works perfectly. Test can be run. But it is NOT a good practice to have an entityManager in a public property
My question is : How to mock entityManager in a service test ?
(Sorry, I am not fluent in english)
First of all in your code
$container->set('doctrine.orm.entity_manager'); // THIS LINE DOES NOTHING <=======
I'm sure you missed second parameter :)
Another note: don't mock what you don't own.
In future you will run composer update and EntityManager's commit will have some optional isReallyCommit parameter. Your code will be broken, but you will not notice it, because tests are green. I understand that it very unlikely, but anyway it's just example. I think the good practice here is to
Have some adapter with entity manager incapsulated
Mock that adapter in service unit tests
Test your adapter with functional tests against real database without mocking anything
Or just don't unit test your service but make functional tests with real database interactions
Related
What would be the proper way to test a service in Symfony 4, which also accesses the database?
I am new in Symfony4 (before I developed for Symfony2) and I want to write my first test for a Service.
This service is writing via Entities / Doctrine / ORM in the database and each of my methods, I want to test is triggering a database save.
In Symfony 2 this was the case when I used rather KernelTestCase instead of the PHPUnit_Framework_TestCase because mocking the EntityManager was a pain in the ass and often I also wanted to check the result in the test db.
All examples for Symfony 4 only mention the KernelTestCase for testing Commands.
My class:
class UserPropertyService implements UserPropertyServiceInterface
{
public function __construct(EntityManager $em, LoggerInterface $logger)
{
....
}
....
}
My attempt at a Test:
class UserPropertyServiceTest extends KernelTestCase
{
/** #var UserPropertyService */
private $userPropertyService;
public function setUp()
{
self::bootKernel();
$client = static::createClient();
$container = $client->getContainer();
$this->userPropertyService = self::$container->get('app.user_management.user_property_service');
}
results in:
Cannot autowire service "App\Service\UserManagement\UserPropertyService": argument
"$em" of method "__construct()" references class "Doctrine\ORM\EntityManager"
but no such service exists.
Try changing the type-hint to one of its parents: interface "Doctrine\ORM\EntityManagerInterface",
or interface "Doctrine\Common\Persistence\ObjectManager".
What is the proper approach here? Which test class should I use?
This is how look like a service test (do not get your Container through the client, those containers are different)
By the way, you can't use static::createClient(); if you extend from KernelTestCase (misunderstanding with controller test and the WebTestCase class ?)
<?php
namespace App\Tests\Service;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class UserPropertyServiceTest extends KernelTestCase
{
/** #var UserPropertyService */
private $myService;
public function setUp() {
self::bootKernel();
$this->myService = self::$kernel->getContainer()->get('app.user_management.user_property_service');
}
}
I am trying to test a ZF3 controller action which, in the process, selects a user from the database via a Doctrine ORM repository using a token given as a GET-Parameter. As I need to make sure that the User exists I need to create a mock of the repository returning the user object. How do I do this?
My setup is the following:
The class UserControllerFactory is instantiating a UserController class:
class UserControllerFactory implements FactoryInterface {
/**
* #param ContainerInterface $container Zend\ServiceManager\ServiceManager
* #param string $requestedName
* #param array|NULL $options
*
* #return UserController
*/
public function __invoke(ContainerInterface $container, $requestedName, Array $options = NULL) {
$entityManager = $container->get('doctrine.entitymanager.orm_default');
$userRepository = $entityManager->getRepository('User\Entity\User');
return new UserController($container, $entityManager, $userRepository);
}}
In the UserController the acton resetPassword is called. It gets the needed parameter from the route and selects a user from the database matching the token:
public function resetPasswordAction() {
$request = $this->getRequest();
$passwordResetToken = $this->params()->fromRoute('token');
if(strlen(trim($passwordResetToken))) {
$user = $this->userRepository->findOneBy(
[
'passwordResetToken' => $passwordResetToken
]
);
...
If no user is found. The action will redirect to user to a different action.
PHPUnit test case:
public function testResetPasswordActionCanBeAccessed() {
$passwordResetToken = 'testToken1234';
$this->dispatch("/user/resetPassword/$passwordResetToken", 'GET');
$this->assertNotRedirect();
}
As there is no user having the token is will be redirected.
To my knowledge I need to create a mock of the repository (userRepository), create a mock user and use the mock repository retrieve the mock user having the token.
I am not sure if this is the right approche as I tried a lot of tutorial and never got it to work. I don't know how to "replace" the, in the action called "userRepository" with the in the unit test created mock object.
I am happy to provide more details if needed.
EDIT
As suggested by #DonCallisto (thank you) I changed my test case code to:
...
$mockedEm = $this->createMock(EntityManager::class);
$mockedUserRepository = $this->createMock('Core\Repository\EntityRepository');
$mockedEm->method('getRepository')->willReturn($mockedUserRepository);
$mockedUserRepository->method('findOneBy')->willReturn($mockedUser);
$this->dispatch("/$this->_lang/user/resetPassword/$passwordResetToken", 'GET');
...
but after calling the "dispatch" in the test case my controller call
$user = $this->userRepository->findOneBy(...)
will still return NULL instead of the mocked user object given in the test. If I debug the $mockedUserRepository, my $mockedUser is assigned correctly.
I also tried the suggested:
$mockedUserRepository->findOneBy([arrayWithParams])->willReturn($mockedUser);
But this will through an error due to the fact that $mockedUserRepository->findOneBy() is returning NULL.
In an effort to learn the logic flow of mock tests I've unsuccessfully reproduced a test from a Symfony article using code from my application.
Background: Volunteer entity extends the abstract class Person, which extends the FOSUserBundle model User. Person includes methods for firstName, lastName, and name. Name returns lastName, firstName. The test that appears below returns this:
--- Expected
+++ Actual
## ##
-'Borko, Benny'
+', '
How should this test be modified? Better yet, how do you tell when it's your test design and not the system under test that's failing?
Edit: see result of applying proposed solution below
Edit #2:
Not sure if this is relevant: Volunteer and two other entities are mapped via Inheritance Mapping to the Person entity (see PUGXMultiUserBundle).
The test:
use Truckee\MatchingBundle\Entity\Volunteer;
class MockVolunteerTest extends \PHPUnit_Framework_TestCase
{
public function testFullName()
{
// First, mock the object to be used in the test
$volunteer = $this->getMock('\Truckee\MatchingBundle\Entity\Volunteer');
$volunteer->expects($this->once())
->method('getFirstName')
->will($this->returnValue('Benny'));
$volunteer->expects($this->once())
->method('getLastName')
->will($this->returnValue('Borko'));
// Now, mock the repository so it returns the mock of the volunteer
$volunteerRepository = $this->getMockBuilder('\Doctrine\ORM\EntityRepository')
->disableOriginalConstructor()
->getMock();
$volunteerRepository->expects($this->once())
->method('find')
->will($this->returnValue($volunteer));
// Last, mock the EntityManager to return the mock of the repository
$em = $this->getMockBuilder('\Doctrine\Common\Persistence\ObjectManager')
->disableOriginalConstructor()
->getMock();
$em->expects($this->once())
->method('getRepository')
->will($this->returnValue($volunteerRepository));
$user = new Volunteer();
$this->assertEquals('Borko, Benny', $user->getName());
}
}
Proposed solution:
class VolunteerTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Volunteer
*/
protected $object;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->object = new Volunteer();
}
public function testGetterAndSetter() {
$this->assertNull($this->object->setFirstName("Benny"));
$this->assertEquals("Benny", $this->object->getFirstName());
$this->assertNull($this->object->setLastName("Borko"));
$this->assertEquals("Borko", $this->object->getLastName());
$this->assertEquals('Borko, Benny', $this->object->getName());
}
}
Test results:
Failed asserting that Truckee\MatchingBundle\Entity\Volunteer Object
&0000000067c9c33f00000000680c6030 (
'id' => null ...
'credentialsExpireAt' => null ) is null.
The main goal of mocks is to test repositories or services. There is a easier way to test your entity:
class VolunteerTest extends \PHPUnit_Framework_TestCase {
/**
* #var Volunteer
*/
protected $object;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->object = new Volunteer();
}
public function testGetterAndSetter() {
$this->assertNull($this->object->setFirstName("Benny"));
$this->assertEquals("Benny", $this->object->getFirstName());
$this->assertNull($this->object->setLastName("Borko"));
$this->assertEquals("Borko", $this->object->getLastName());
}
}
The answer here is that the test is an inappropriate use of mock testing. After reading the article "An Introduction to Mock Object Testing" it became clear that the technique is to mock the dependencies of the system (object) under test, not the object itself. In the test I attempted, the Volunteer entity was the SUT so it should not have been mocked.
I am using the CakePHP-ReST-DataSource-Plugin Datasource for hitting a RESTful service in my model. This implies that the models will not have a database connection.
I have successfully accessed the services and would now like to write unit tests for the models. This is proving to be a daunting task since I cannot succeed to mock the datasource so that I do not hit the actual remote Service but rather return expected results for the tests.
<?php
App::uses('KnowledgePoint', 'Model');
class KnowledgePointTest extends CakeTestCase{
public $fixtures = array('app.knowledgepoint');
public $useDbConfig = 'RestTest';
private $KnowledgePoint;
public function setUp() {
parent::setUp();
$this->KnowledgePoint = ClassRegistry::init('KnowledgePoint');
/**
* This is the confusing part. How would I mock the datasource
so that I can mock the request method which returns the data
from the api?
*/
$this->KnowledgePoint->DataSource = $this->getMockForModel(
'RestSource',array('request'));
}
public function tearDown() {
parent::tearDown();
}
}
I would like to be able to mock the datasource and stub the request method to return data that would normally be returned from the remote service.
Kind regards,
Roland
Mocking the model and its getDataSource() method so that it returns your mocked datasource should theoretically work. Here's an example
App::uses('RestSource', 'Rest.Model/Datasource');
$DataSource = $this->getMock('RestSource', array('request'), array(array()));
$DataSource
->expects($this->any())
->method('request')
->will($this->returnValue('some custom return value'));
$Model = $this->getMockForModel('KnowledgePoint', array('getDataSource'));
$Model
->expects($this->any())
->method('getDataSource')
->will($this->returnValue($DataSource));
$Model->save(/* ... */);
In case you are wondering about the array(array()) for the datasource mock, this is required as the RestSource constructor doesn't supply a default value for the first argument (unlike the parent constructor).
I have a custom entity repository. For example, it looks like this:
namespace Foo\Repository;
use Doctrine\ORM\EntityRepository;
class Article extends EntityRepository
{
public function findRecent($limit)
{
$qb = $this->createQueryBuilder('a');
$qb->andWhere('a.publishDate IS NOT NULL')
->orderBy('a.publishDate', 'DESC')
->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
}
I want to test in this case:
There is an ORDER BY in "recent"
There is a limit
The entity must have a publish date
I do not want to validate the SQL output of the query builder, since Doctrine can change the SQL between different versions. That will break my unit test. Therefore, my idea was this:
Create a mock of my repository
Create a mock of the query builder
Make sure $this->createQueryBuilder('a') returns the mocked query builder
Test for method calls on the query builder
In code:
namespace FooTest\Repository;
use PHPUnit_Framework_TestCase as TestCase;
class ArticleRepositoryTest extends TestCase
{
protected $qb;
protected $repository;
public function setUp()
{
$this->qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder')
->disableOriginalConstructor()
->getMock();
$this->repository = $this->getMockBuilder('Foo\Repository\Article')
->disableOriginalConstructor()
->getMock();
$this->repository->expects($this->once())
->method('createQueryBuilder')
->with($this->equalTo('a'))
->will($this->returnValue($this->qb));
}
public function testFindRecentLimitsToGivenLimit()
{
$limit = '1';
$this->qb->expects($this->any())
->method('setMaxResults')
->with($this->equalTo($limit));
$this->repository->findRecent($limit);
}
public function testFindRecentOrdersByPublishDate()
{
$this->qb->expects($this->any())
->method('andWhere')
->with($this->equalTo('a.publishDate'), $this->equalTo('DESC'));
$this->repository->findRecent(1);
}
}
This findRecent() call however never calls createQueryBuilder internally. PhpUnit points out:
FooTest\Repository\ArticleRepositoryTest::testFindRecentLimitsToGivenLimit
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
I think I did something wrong in creating the repository mock. How can I make sure this approach works? Or if there is a better alternative, what is that?
It looks to me like you are mocking the Repository you are trying to test, so findRecent() is indeed mocked and will return null.
You should be able to use the real repository instance.
The solution I found to testing subclassed repositories is to add a call to setMethodsExcept() when building the mock.
So you would modify your code within setUp() above like so:
$this->repository = $this->getMockBuilder('Foo\Repository\Article')
->disableOriginalConstructor()
->setMethodsExcept([
// Insert any overridden/implemented functions here, in your case:
'findRecent',
])
->getMock();