XF 2.0 PHPUnit

Earl

Active member
#1
Does anybody use PHPUnit with XenForo 2.x?
How do you setup the working environment with PHPUnit?
And PHPStorm?
I can see some vendor packages have already used it (PHPUnit\Framework\TestCase) but can we use it when we're developing add-ons?
 

Ralle

Active member
#2
I'm using it.

In my src/addons/YourAddon/Tests I have a bootstrap.php file with these contents:
PHP:
<?php

$dir =
    __DIR__ . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..';

require ($dir . '/src/XF.php');

XF::startAutoloader($dir);
Which basically just tells PHPUnit how to XenForo classes.

And also this file
XML:
<?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="./bootstrap.php" colors="true">
</phpunit>
I put all my tests in src/addons/YourAddon/Tests and have this header:
PHP:
namespace YourAddon\Tests;

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    use TestCaseTrait;
And I run it like this:
Code:
cd src/addons/YourAddon/Tests && phpunit .
PHPUnit always looks for the phpunit.xml in the current directory, so we have to go there to run our tests.

This is for XF2, let me know if you need help with XF1.

I am also using DbUnit, so if you need help with that, I can share more of my code.
 

Earl

Active member
#3
I'm using it.

In my src/addons/YourAddon/Tests I have a bootstrap.php file with these contents:
PHP:
<?php

$dir =
    __DIR__ . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..';

require ($dir . '/src/XF.php');

XF::startAutoloader($dir);
Which basically just tells PHPUnit how to XenForo classes.

And also this file
XML:
<?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="./bootstrap.php" colors="true">
</phpunit>
I put all my tests in src/addons/YourAddon/Tests and have this header:
PHP:
namespace YourAddon\Tests;

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    use TestCaseTrait;
And I run it like this:
Code:
cd src/addons/YourAddon/Tests && phpunit .
PHPUnit always looks for the phpunit.xml in the current directory, so we have to go there to run our tests.

This is for XF2, let me know if you need help with XF1.

I am also using DbUnit, so if you need help with that, I can share more of my code.

Hey @Ralle, thank you so much for trying to help us with this, I've been struggling for days to make it work in your way, but I got some errors


Edit: Okay I found a solution:
Changing this line in your bootstrap.php
Code:
XF::startAutoloader($dir);
to this:
Code:
XF::start($dir);
Fixed a lot of things but still, I can't call a method to an XFCP class from a Test.

Let's say I have a class extension to this ResourceUpdate entity, and I try to call it from a Test like this:
PHP:
public function testCanAddResourceWithoutWatermarkPermission()

{


    /** @var \Earl\MyAddon\XFRM\Entity\ResourceUpdate $ResourceUpdate */

    $ResourceUpdate=  \XF::em()->create('XFRM:ResourceUpdate');


    $this->assertTrue($ResourceUpdate->canAddResourceWithoutRelations());


}
It gives an error:
Code:
Error : Call to undefined method XFRM\Entity\ResourceUpdate::canAddResourceWithoutRelations()
/opt/project/code/src/addons/Earl/MyAddon/Tests/ResourceUpdateTest.php:36
 
Last edited:

Aayush

Well-known member
#4
I'm using it.

In my src/addons/YourAddon/Tests I have a bootstrap.php file with these contents:
PHP:
<?php

$dir =
    __DIR__ . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..' . DIRECTORY_SEPARATOR .
    '..';

require ($dir . '/src/XF.php');

XF::startAutoloader($dir);
Which basically just tells PHPUnit how to XenForo classes.

And also this file
XML:
<?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="./bootstrap.php" colors="true">
</phpunit>
I put all my tests in src/addons/YourAddon/Tests and have this header:
PHP:
namespace YourAddon\Tests;

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    use TestCaseTrait;
And I run it like this:
Code:
cd src/addons/YourAddon/Tests && phpunit .
PHPUnit always looks for the phpunit.xml in the current directory, so we have to go there to run our tests.

This is for XF2, let me know if you need help with XF1.

I am also using DbUnit, so if you need help with that, I can share more of my code.
If possible, can you share a little about the tests that you've written for your addon and what all do you test with XF?
 

Ralle

Active member
#5
Unit tests can be a good way of learning how things work. For example, I wanted to understand how equality works in PHP with objects and with XF entities and I wrote this:
PHP:
<?php

namespace VindIT\Repository\Tests;

use PHPUnit\Framework\TestCase;
use XF;

class EntityTest extends TestCase
{
    public function testObjectIsNotSame()
    {
        $a = new \stdClass;
        $b = new \stdClass;
        $this->assertNotSame($a, $b);
    }

    public function testObjectIsSame()
    {
        $a = new \stdClass;
        $b = $a;
        $this->assertSame($a, $b);
    }

    public function testObjectIsNotEqual()
    {
        $a = new \stdClass;
        $b = new \stdClass;
        $this->assertEquals($a, $b);
    }

    public function testObjectIsEqual()
    {
        $a = new \stdClass;
        $b = $a;
        $this->assertEquals($a, $b);
    }

    public function testEntityIsSame()
    {
        $e1 = \XF::app()->finder('XF:User')->whereId(1)->fetchOne();
        $e2 = \XF::app()->finder('XF:User')->whereId(1)->fetchOne();
        $this->assertSame($e1, $e2, 'Two instances of same entity are the same');
    }

    public function testEntityIsEqual()
    {
        $e1 = \XF::app()->finder('XF:User')->whereId(1)->fetchOne();
        $e2 = \XF::app()->finder('XF:User')->whereId(1)->fetchOne();
        $this->assertEquals($e1, $e2, 'Two instances of same entity are equal');
    }

    public function testChangeValue()
    {
        $e1 = \XF::app()->finder('XF:User')->whereId(1)->fetchOne();
        $e2 = \XF::app()->finder('XF:User')->whereId(1)->fetchOne();
        $e1->set('username', 'Ralle1');
        $this->assertSame($e1->get('username'), $e2->get('username'));
    }

}
I learned that two entities of the same ID and type ARE the same object. Very interesting.

I also use it for test-driven development things in my own code.

@Earl, I have not had any issues with class extensions. Your code looks to be right. Not sure what to tell you.
 

DragonByte Tech

Well-known member
#6
I learned that two entities of the same ID and type ARE the same object. Very interesting.
That's not technically correct. The EntityManager in XF2 has a cache system, and since you've told it to look up based on primary key, it will use the entity cache. Therefore, your call to $e2 never hit the database and never generated a new copy of the object.

In this case, the test is meaningless and the best way to learn about the inner workings of the entity system is to read the EntityManager class.

By the way, a shorthand of \XF::app()->finder('XF:User')->whereId(1)->fetchOne(); is \XF::em()->find('XF:User', 1);


Fillip
 

Ralle

Active member
#7
Hey Fillip. Thanks for more information. The entity thing sort of scared me a bit. I kind of like the idea of an exception occurring to reset edits made on an entity by its reference being lost. I guess you have to manually flush it from the cache to forget it.
 

DragonByte Tech

Well-known member
#8
Hey Fillip. Thanks for more information. The entity thing sort of scared me a bit. I kind of like the idea of an exception occurring to reset edits made on an entity by its reference being lost. I guess you have to manually flush it from the cache to forget it.
I'm not sure what exactly you're trying to say there, but if you want to reset an entity after modifying it (before saving it), call its reset() method.


Fillip
 
Top