Silverstripe 3.7 Unit Tests - tearDownOnce() and setUpOnce() do not trigger - silverstripe

Silverstripe 3.7.6, PHP 7.3, PHPUnit 4.8
We have a bunch of existing unit tests that use SapphireTest->setUpOnce() and SapphireTest->tearDownOnce(). The latter is used to destroy test databases once complete. Which is how we found that these methods were not firing.
Simple test that simulates
<?php
class MyTest extends SapphireTest {
protected $usesDatabase = true;
public function setUp()
{
echo "setup \n";
parent::setUp();
}
public function tearDown()
{
echo "teardown \n";
parent::tearDown();
}
public function setUpOnce()
{
echo "setup once \n";
parent::setUpOnce();
}
public function tearDownOnce()
{
echo "teardown once \n";
parent::tearDownOnce();
}
function testSomething()
{
// My test
}
}
Output when test is run:
PHPUnit 4.8.36 by Sebastian Bergmann and contributors.
.setup teardown
Time: 6.38 seconds, Memory: 80.50MB
OK (1 test, 0 assertions)
Is there something that needs to be done in the test so these functions are fired at the start and end of the whole test class per docblock?
UPDATE:
See convo below. It was suggested that I use PHPUnit's setUpBeforeClass and tearDownAfterClass methods instead. These fired as expected. However they are static methods so I had no access to $this to use supporting SapphireTest or custom methods in the class. So I had to use these PHPUnit methods to instantiate an instance of the test class they sit in, then call my existing setUpOnce and tearDownOnce methods. I used late static binding's static() keyword here as I might move these calls into a parent class for my tests all to use. A bit hacky, but it seems to work.
Assuming my test code above, below is the addition to make:
public static function setUpBeforeClass() {
$object = new static();
$object->setUpOnce();
}
public static function tearDownAfterClass() {
$object = new static();
$object->tearDownOnce();
}

Related

phpunit custom teardown specific to my test

I have some setup specific to my test in a class. Since it is specific to my test, I have added to it to the top of my test function. The cleanup is added to the end of the test function. The problem when the test fails and the cleanup does not gets executed. Is there a PHPUnit way to specify a custom teardown specific to my test function. I looked at the PHPUnit manual which specifies teardownAfterClass and tearDown, but both does not solve my problem. The function teardownAfterClass will run only once at the end of the class. The function teardown runs after each test, but I do not want to do any cleanup if my specific test function was not executed.
What is the PHPUnit way of creating custom teardown function for my test?
Here is the code which I use to make sure that the cleanup specific to the test always happen, but it is ugly as it needs to put the actual test in a separate function and needs try/catch block. Is there is a PHPUnit specific way to handle it? Something like dataProvider specific to function will be great, which always gets executed after the test irrespective of failure or success.
class testClass {
public function test1() {
self::setupSpecificToTest1();
try {
// actual test without cleanup
$this->_test1();
} catch (Exception $e) {
self::cleanupSpecificToTest1();
throw $e;
}
self::cleanupSpecificToTest1();
}
public function test2() {
// some code which does not need any setup or cleanup
}
private function _test1() {
// some test code
}
}
Implement intelligent tearDown (it's closer to PHPUnit's approach to running tests)
You can check for the specific test name inside tearDown method to alternate its behaviour accordingly. Getting test name can be done via $this->getName() in the test class.
Try something like:
...
public function test1() { ... }
public function test2() { ... }
public function tearDown()
{
if ($this->getName() === 'test1')
{
// Do clean up specific to test1
}
parent::tearDown();
}
I have tried this, it worked for me after my test.
public function tearDown()
{
$this->webDriver->close();
}

Passing data provider to setUp() in PHPUnit

I'm currently trying to pass data from my data provider to the setUp()-method in PHPUnit.
Background: I am using PHPUnit for running frontend-tests in different browsers. The browser should be defined inside the data provider and needs to be known by the setUp()-method.
I understand, that a data provider initially is executed before the setUp()-method (as setUpBeforeClass()) is called. Therefore setUp()-data can not be passed to a data provider. But it should work the other way round, shouldn't it?
Does PHPUnit generate its own temporarily testclasses with data from the data provider "integrated"?
Of course: a workaround could be, to read the XML-file in the setUp()-method again. But that's the last option, I'd consider...
EDIT: Provided a small snippet:
part of dataProvider():
public function dataProvider()
{
$this->xmlCnf = $data['config'];
var_dump($this->xmlCnf); // array with config is exposed
// [...]
}
And the setUp()-method:
protected function setUp()
{
var_dump($this->xmlCnf); // NULL
//[...]
}
In case this is useful to anyone:
The following code should work:
public function dataProvider()
{
return [ [ /* dataset 1 */] , ... ]
}
protected setUp() {
parent::setUp();
$arguments = $this->getProvidedData();
// $arguments should match the provided arguments for this test case
}
/**
* #dataProvider dataProvider
*/
public function testCase(...$arguments) {
}
The getProvidedData method seems to have been available since PHPUnit 5.6 (which was either shortly before or after this question was originally asked)
we can make the xmlCnf to static
private static $xmlCnf;
public function provider(){
self::$xmlCnf = 'hello';
var_dump(self::$xmlCnf); //hello
return [...];
}
public function setUp() {
var_dump(self::$xmlCnf); //hello
parent::setUp();
}

In phpunit what is the difference between __construct versus setup?

I am curious to know it is good practice to create object in test class __construct or we should always use setup/teardown approach ( or setUpBeforeClass/tearDownAfterClass approach)?
I aware of the fact set/teardown gets called for each test so will it do any good if I put my object creation code in it? e.g.
//mytestclass.php
class MyTestClass extends PHPUnit_Framework_TestCase
{
private $obj;
protected function setUp()
{
$this->obj = new FooClass();
}
public testFooObj()
{
//assertions for $this->obj
}
...
}
what could be the issues if I create object in constructor like this:
class MyTestClass extends PHPUnit_Framework_TestCase
{
private $obj;
protected function __construct()
{
$this->obj = new FooClass();
}
public testFooObj()
{
//assertions for $this->obj
}
...
}
I tried googling around as well as PHPUnit documentation couldn't get much information about, Can you please help me to understand which one is good practice?
setUp() gets called before each of your tests is ran. __construct() happens when your class is instantiated. So if you have multiple tests and they use local properties and modify them, using setUp() you can ensure that they are the same before each test is ran. The opposite of setUp() is tearDown() where you can ensure that test data gets cleaned up after each test.
As I have just found out, implementing the default class constructor instead of the setupBeforeClass() method breaks the #dataProvider annotations (probably all kinds of annotations), yielding a "Missing argument" exception for any parameterized tests.
Missing argument 1 for AppBundle\Tests\Service\InvitationVerifierTest::testDireccionInvalida()
Replacing public function __construct() for public static function setUpBeforeClass() gets rid of the exception. So there it goes, favor the setupBeforeClass() method over the regular constructor.
PHPUnit version 4.5.0

flexunit: Parametrized tests

I am trying to run a parametrized tests... Was trying to implement it like it explained here:
http://docs.flexunit.org/index.php?title=Parameterized_Test_Styles
Here is what my test case looking
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
[Paremeters]
public static var stackProvider:Array = [new ArrayBasedStack(), new LinkedListBasedStack()] ;
private var _stack:IStack;
public function ArrayBasedStackTests(param:IStack)
{
_stack = param;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stackProvider" )]
public function isEmptyStackPositiveTest():void
{
var stack:IStack = _stack;
assertEquals( true, stack.isEmpty() );
}
But this code throws following initializing Error:
Error: Custom runner class org.flexunit.runners.Parameterized should
be linked into project and implement IRunner. Further it needs to have
a constructor which either just accepts the class, or the class and a
builder.
Need help to fix it
UPDATE
I've updated the code so it looks like this
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
private var foo:Parameterized;
[Parameters]
public static function stacks():Array
{
return [ [new ArrayBasedStack()], [new LinkedListBasedStack()] ] ;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stacks")]
public function isEmptyStackPositiveTest(stack:IStack):void
{
assertEquals( true, _stack.isEmpty() );
}
It works. But the result is a bit strange. I have 4 test executed instead of 2. (I have 2 items in data provider, so cant get why do I have 4 tests).
Output
http://screencast.com/t/G8DHbcjDUkJ
The [Parameters] meta-data specifies that the parameters are passed to the constructor of the test - so the test class is called for each parameter. You also have the dataProvider set for the specific test method, so the test method is also called once for each parameter. Two calls for the test, and two calls to the method, ends up running four tests.
The solution is to either use [Parameters] meta-tag which specifies the data to use for the whole test class, or use the dataProvider for each test method, but not both with the same data at the same time.
You're missing the static reference to Paramaterized, as shown here:
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class MyTestNGTest
{
private var foo:Parameterized;
...
Basically, that error means that the [Runner] defined isn't available at runtime, which occurs if there is no static reference in the class to cause it to get linked in.
In FlexUnit 4.5.1, this approach changed to using [Rule]'s like so:
public class MyTestNGTest
{
[Rule]
public function paramaterizedRule:ParamaterizedRule = new ParamaterizedRule();
...
}
However, I can't seem to see an actual implementation of IMethodRule for paramaterized tests (that example is fictional).

How to catch PHP Warning in PHPUnit

I am writing test cases and here is a question I have.
So say I am testing a simple function someClass::loadValue($value)
The normal test case is easy, but assume when passing in null or -1 the function call generates a PHP Warning, which is considered a bug.
The question is, how do I write my PHPUnit test case so that it succeeds when the functions handles null/-1 gracefully, and fail when there is a PHP Warning thrown?
PHPUnit_Util_ErrorHandler::handleError() throws one of several exception types based on the error code:
PHPUnit_Framework_Error_Notice for E_NOTICE, E_USER_NOTICE, and E_STRICT
PHPUnit_Framework_Error_Warning for E_WARNING and E_USER_WARNING
PHPUnit_Framework_Error for all others
You can catch and expect these as you would any other exception.
/**
* #expectedException PHPUnit_Framework_Error_Warning
*/
function testNegativeNumberTriggersWarning() {
$fixture = new someClass;
$fixture->loadValue(-1);
}
I would create a separate case to test when the notice/warning is expected.
For PHPUnit v6.0+ this is the up to date syntax:
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\Error\Warning;
use PHPUnit\Framework\TestCase;
class YourShinyNoticeTest extends TestCase
{
public function test_it_emits_a_warning()
{
$this->expectException(Warning::class);
file_get_contents('/nonexistent_file'); // This will emit a PHP Warning, so test passes
}
public function test_it_emits_a_notice()
{
$this->expectException(Notice::class);
$now = new \DateTime();
$now->whatever; // Notice gets emitted here, so the test will pass
}
}
What worked for me was modifying my phpunit.xml to have
<phpunit
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
strict="true"
>
</phpunit>
The key was to use strict="true" to get the warnings to result in a failed test.
You can also write a phpunit.xml file (on your tests dir) with this:
<phpunit
convertErrorsToExceptions="true"
convertNoticesToExceptions="false"
stopOnFailure="false">
</phpunit>
Using Netsilik/BaseTestCase (MIT License) you can test directly for triggered Errors/Warnings, without converting them to Exceptions:
composer require netsilik/base-test-case
Testing for an E_USER_NOTICE:
<?php
namespace Tests;
class MyTestCase extends \Netsilik\Testing\BaseTestCase
{
/**
* {#inheritDoc}
*/
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->_convertNoticesToExceptions = false;
$this->_convertWarningsToExceptions = false;
$this->_convertErrorsToExceptions = true;
}
public function test_whenNoticeTriggered_weCanTestForIt()
{
$foo = new Foo();
$foo->bar();
self::assertErrorTriggered(E_USER_NOTICE, 'The warning string');
}
}
Hope this helps someone in the future.
public function testFooBar(): void
{
// this is required
$this->expectWarning();
// these are optional
$this->expectWarningMessage('fopen(/tmp/non-existent): Failed to open stream: No such file or directory');
$this->expectWarningMessageMatches('/No such file or directory/');
fopen('/tmp/non-existent', 'rb');
}
Make SomeClass throw an error when input is invalid and tell phpUnit to expect an error.
One method is this:
class ExceptionTest extends PHPUnit_Framework_TestCase
{
public function testLoadValueWithNull()
{
$o = new SomeClass();
$this->setExpectedException('InvalidArgumentException');
$this->assertInstanceOf('InvalidArgumentException', $o::loadValue(null));
}
}
See documentation for more methods.

Resources