How do I use PHPUnit to test __construct with arguments? - php

I'm new to PHPUnit, and unit testing in general. I can't seem to find a clear tutorial or resource on how best to test:
Passing no argument fails.
How to pass an argument for a constructor test.
Passing an empty argument results in the expected exception.
How would I approach testing this constructor?
<?php
class SiteManagement {
public function __construct (array $config) {
// Make sure we actually passed a config
if (empty($config)) {
throw new \Exception('Configuration not valid', 100);
}
// Sanity check the site list
if (empty($config['siteList'])) {
throw new \Exception('Site list not set', 101);
}
}
}

The example 2.11 of the PHPUnit documentation shows how to test exceptions.
For your specific class, it would be something like this:
$this->expectException(Exception::class);
$object = new SiteManagement([]);
You shouldn't test if the method fails without arguments unless those arguments are optional. This would be out of the scope for a unit test.

A good test battery for your class would be:
/** #test */
public function shouldFailWithEmptyConfig(): void
{
$config = [];
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Configuration not valid');
$this->expectExceptionCode(100);
new SiteManagement($config);
}
/** #test */
public function shouldFailWithoutSiteListConfig(): void
{
$config = ['a config'];
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Site list not set');
$this->expectExceptionCode(101);
new SiteManagement($config);
}
/** #test */
public function shouldMakeAFuncionality(): void
{
$config = [
'siteList' => '',
];
$siteManagement = new SiteManagement($config);
self::assertSame('expected', $siteManagement->functionality());
}

Related

#dataProvider or #depends can I use it?

I have a class (called FormFilters), that class calls its methods within one method, in this case getProject.
class FormFilters extends KernelTestCase
{
public function getProject($filters)
{
$this->filters = $filters;
$this->getWhere($this->filters);
}
public function getWhere()
{
if ($this->filters->isValid()) {
$this->sql = $this->filterName($this->filters->get('name')->getData());
}
}
public function filterName()
{
//....
}
}
This is getProject method test:
public function test_getProject()
{
$formInterface = $this->createMock('Symfony\Component\Form\FormInterface');
$formInterface
->expects($this->at(0))
->method('isValid')
->willReturn(true); // come into conditional
$formInterface
->expects($this->at(1))
->method('get')
->with('name')
->will($this->returnSelf());
$formInterface
->expects($this->at(2))
->method('getData')
->will('data example');
$formFilters = new FormFilters();
$formFilters->getProject($formInterface); // my mock
}
So far all right. Now, I want to test getWhere method, I could do it independently, but if getProject has the same test (called to getWhere method), could I use the annotations #dataProvider or #depends, like this (example) :
/**
* #depends or/and #dataProvider test_getProject
*/
public function test_getWhere($dataToDepends)
{
// ... test ready !
}
It's possible ?
In your current set-up, positive case for getWhere() is already tested (in scope of test_getProject()). So, what is left to test in getWhere() is a negative case, when interpreter does not go inside of IF.
Test could be:
public function test_getWhere_invalid_filters()
{
$formInterface->expects($this->once())
->method('isValid')
->willReturn(false);
$formInterface->expects($this->never())
->method('get');
$formInterface->expects($this->never())
->method('getData');
$formFilters = new FormFilters();
//todo: inject $formInterface into $formFilterssomehow at this line.
$formFilters->getWhere();
}
Regarding your question with #depends - it's usually used when second test can not be executed before first is done. For example, first case creates some entity in database, and second test tries to delete entity, created in previous test. Another example - a static property of class, set in one test and expected to be read in another test. Generally speaking, having dependent tests, as well as dependent code units is not encouraged. And anyway, it's not your case, not what you need for the test.
Regarding #dataProvider - it's pretty usefull annotation. It allows to separate logic of the test from tested data. And also it allows to re-use same test with different data sets. Test, posted above, with #dataProvider will look like:
/**
* #dataProvider getWhere_invalid_filters_data_provider
*/
public function test_getWhere_invalid_filters($isValid, $getCallsCount, $getDataCallsCount)
{
$formInterface->expects($this->once())
->method('isValid')
->willReturn($isValid);
$formInterface->expects($this->exactly($getCallsCount))
->method('get');
$formInterface->expects($this->exactly($getDataCallsCount))
->method('getData');
$formFilters = new FormFilters();
//todo: inject $formInterface into $formFilterssomehow at this line.
$formFilters->getWhere();
}
public function getWhere_invalid_filters_data_provider()
{
return [
'case 1' => [
'isValid' => false,
'getCallsCount' => 0,
'getDataCallsCount' => 0,
],
];
}

PHPUnit - ReturnValueMap of Mocks not finding Mocked Class Methods

I'm new to unit testing and have come across something I don't understand when using returnValueMap() in PHPUnit. I've been googling for days now ...
Consider this code under test;
public function __construct(EntityManager $entityManager, AuditLog $auditLog) {
$this->entityManager = $entityManager;
$this->auditLog = $auditLog;
}
public function updateSomeId($oldId, $newId)
{
$repositories = ['repo1', 'repo2', 'repo3'];
foreach ($repositories as $repository) {
try {
$this->entityManager->getRepository($repository)
->updateSomeId($oldId, $newId);
} catch (RepositoryException $e) {
throw new SomeOtherException($e->getMessage(), $e->getCode(), $e);
}
}
}
The unit test code;
... code removed for brevity
$repoMaintenance = new RepoMaintenance(
$this->getEntityManagerMock(),
$this->getAuditLogMock()
);
$this->assertTrue($repoMaintenance->updateSomeId(
$this->oldId,
$this->newId
));
/**
* #return EntityManager
*/
private function getEntityManagerMock()
{
$entityManagerMock = $this->getMockBuilder(EntityManager::class)
->disableOriginalConstructor()
->getMock();
$entityManagerMock
->method('getRepository')
->willReturn($this->returnValueMap($this->getEntityManagerReturnValueMap()));
return $entityManagerMock;
}
/**
* #return array
*/
private function getEntityManagerReturnValueMap()
{
return [
['repo1', $this->getRepo1Mock()],
['repo2', $this->getRepo2Mock()],
['repo3', $this->getRepo3Mock()],
];
}
/**
* #return Repo1
*/
private function getRepo1Mock()
{
return $this->getMockBuilder(Repo1::class)
->disableOriginalConstructor()
->getMock();
}
... Code removed for brevity
When the unit test is run, the following fatal error is returned;
PHP Fatal error: Call to undefined method PHPUnit_Framework_MockObject_Stub_ReturnValueMap::updateSomeId()
I've previously used mocks in return value maps with no issue accessing methods in a public context. The difference is I'm attempting to mock __construct() variables, which are set to private access within the SUT.
What am I missing? The problem (I would naively guess) is with the private access level of the members being mocked.
Is there a way to unit test this code? I don't want to hit the database at any point and this is the reason for mocking the calls to it.
You should have will($this->returnValueMap... instead of willReturn($this->returnValueMap...

Fixtures' references disapearing after one test

In my LoadFixture.php, I add reference to all my fixtures like this :
public function load(ObjectManager $manager) {
$user = new user("Dummy");
$this->persist($user);
$this->addReference("user", $user);
}
In my test class I load them like this :
public function setUp() {
if(self::$do_setup){
$this->loadFixtures(array(
"Bundle\\Tests\\Fixtures\\LoadUser"
)) ;
}
}
In my tests I use them like this :
public function testOne() {
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/');
$this->assertStatusCode(200, $client);
self::$do_setup=false;
}
public function testTwo() {
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/home');
$this->assertStatusCode(200, $client);
}
The thing is, technically, I dont need to use setUp() for each test, so I use $do_setup and a if to execute setUp if needed.
But if I dont execute the setUp() in my testTwo, while my fixtures are in my database, $this->getReference("user_a") is giving me an error :
Call to a member function getReferenceRepository() on a non-object
How can I solve that ?
UPDATE
I have found a solution. So I post it here, just in case someone face the same problem as me.
Many thanks to #Damien Flament for his answer, regarding the fact that the TestCase is deleted after each test.
I changed the name of my setUp() method to open(), and my tearDown() method to close().
The first method of the class call the open() method, and now return $this.
The next method is annoted #depends testOne and take a parameter.
With this parameter I can use my references again.
Ex :
// new setUp Metod
public function open() {
if(self::$do_setup){
$this->loadFixtures(array(
"Bundle\\Tests\\Fixtures\\LoadUser"
)) ;
}
}
//new tearDown method
public function close() {
$this->getContainer()->get('doctrine.orm.entity_manager')->getConnection()->close();
}
public function testOne() {
$this->open();
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/');
$this->assertStatusCode(200, $client);
return $this;
}
/**
* #depends testOne
*/
public function testTwo($it) {
$client = $this->createClient($it->getReference("user_a"));
$client->request('GET', '/home');
$this->assertStatusCode(200, $client);
return $it;
}
/**
* #depends testTwo
*/
public function testThree($it) {
$client = $this->createClient($it->getReference("user_a"));
$client->request('GET', '/about');
$this->assertStatusCode(200, $client);
$this->close();
}
I think the TestCase object is deleted and recreated by PHPUnit (I didn't read the PHPUnit source code, but I think it's the more easy way to reset the testing environment for each test).
So your object (probably referenced by a test class object attribute) is probably garbage collected.
To setup fixture once per test class, use the TestCase::setUpBeforeClass() method.
See documention on "Sharing fixtures".

PHPUnit mocked method returns null

I am trying to test the below class using PHPUnit
class stripe extends paymentValidator {
public $apiKey;
public function __construct ($apiKey){
$this->apiKey = $apiKey;
}
public function charge($token) {
try {
return $this->requestStripe($token);
} catch(\Stripe\Error\Card $e) {
echo $e->getMessage();
return false;
}
}
public function requestStripe($token) {
// do something
}
}
My test scripts is like the below:
class paymentvalidatorTest extends PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function test_stripe() {
// Create a stub for the SomeClass class.
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe', 'charge'])
->getMock();
$stripe->expects($this->any())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
}
}
With my test script I was expecting the test double of stripe::charge() method will do exactly as the defined in the original class and the stripe::requestStripe() will return 'Miaw'. Therefore, $stripe->charge('token') should also return 'Miaw'. However, when I run the test I get:
Failed asserting that null matches expected 'Miaw'.
How should I fix this ?
Where you're calling setMethods, you're telling PHPUnit that the mock class should mock the behaviour of those methods:
->setMethods(['requestStripe', 'charge'])
In your case it looks like you want to partially mock the class, so that requestStripe() returns Miaw, but you want charge to run its original code - you should just remove charge from the mocked methods:
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe'])
->getMock();
$stripe->expects($this->once())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
While you're at it you may as well specify how many times you expect requestStripe() to be called - it's an extra assertion with no extra effort, as using $this->any() doesn't provide you with any added benefit. I've included using $this->once() in the example.

Mockery/Etsy PHPExtensions does not fail test if required methods are not called

I have the below code, which I would expect to fail when run as the class DoesNothing doesn't use the mock class or call any of the required methods on it.
<?php
class DoesNothing
{
}
class DoesNothingTest extends YourMockeryTestCase
{
/**
* #test
*/
public function somethingIsCalled()
{
$this->mock = Mockery::mock();
$keys = array(
'1234',
'abcxyz',
'*&(%&^$-*/~#:{}',
')*&GA^FAUIB(*',
'',
' ',
);
foreach ($keys as $key) {
$this->mock
->shouldReceive('remove')
->atLeast()->times(1)
->with($key);
}
$var = new DoesNothing($this->mock);
}
}
But when I run it, it passes. I would expect it to say "method remove was not called" etc.
What could be wrong? Something to do with how Mockery Talks to PHPUnit?
Thanks,
Martin
Edit:
I shoudl also mention we are using Etsy's PHPExtensions to integrate it into PHPUnit
Your method name should start with test, otherwise PHPUnit will not determine it as test.
public function testSomethingIsCalled()
edit
You have to call Mockery::close() in your teardown method for expectations to be executed. i.e.
/**
* Tear down
*/
public function tearDown()
{
\Mockery::close();
}

Categories