Viewed   114 times

I have a PHPUnit test case, in which I am puzzled by the following snippet. I want to check that the method actionUpload calls the function exposeAndSaveDataLines correctly, i.e. that the first argument is an array as I expect it to be.

public function test_actionUpload()
{
    $sut = $this->getMockBuilder('MasterdataController')
                ->setMethods(array('exposeAndSaveDataLines', 'render'))
                ->disableOriginalConstructor()
                ->getMock();

    $expectedLines = require_once ($this->dataDir . 'expectedLines.php');

    $sut->expects($this->once())
        ->method('exposeAndSaveDataLines')
        ->with($this->equalTo($expectedLines),
            $this->anything(),
            $this->anything(),
            $this->anything(),
            $this->anything());

    $sut->actionUpload();
}

The expected data is a printout of the current array, made with a temporary print_r (var_export($lines)) in the actual code. I return it in the file expectedLines.php, and when I manually print it, it is correct.

Now, when I run the test case with a single character deliberately misspelled in expectedLines, I get the following error (as expected).

Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
             3 => 'Colour Group Code'
-            4 => '{2F30E832-D3DB-447E-B733-7BC5125CBCCc}'
+            4 => '{2F30E832-D3DB-447E-B733-7BC5125CBCCC}'
         )
     )
 )

However, when I correct the mistake, it still mentions that the two arrays are not equal. However, it now prints the entire array (at least the start of it, it is a long array), but it doesn't show any differences (no - and + in front of any line). Why does the expects method not recognize that the two arrays are the same? How am I able to test this properly?

EDIT 1

I have shortened the array, such that it prints the entire array when they are not equal. Still no + or - signs in the comparison.

This is the end of my expectation PHP file.

    'RetTarget Area' => array(
        0 => array(
            0 => '',
            1 => '',
            2 => '{C19D52BC-834C-45DA-B17F-74D73A2EC0BB}
'
        ),
        1 => array(
            0 => '1',
            1 => '1',
            2 => '{5E25C44F-C18A-4F54-B6B1-248955A82E59}'
        )
    )
);

This is the end of my comparison output in the console.

     'RetTarget Area' => Array (
         0 => Array (
             0 => ''
             1 => ''
             2 => '{C19D52BC-834C-45DA-B17F-74D73A2EC0BB}
             '
         )
         1 => Array (...)
     )
 )

I find it suspicious that the last Array is not fully shown in the comparison.

EDIT 2

I find here that the order of the arrays is important. I am pretty sure though I have all elements in the same order, if PHP is not doing something secret under the hood. The solution mentioned there I cannot copy, since I don't have a $this->assertEquals but a ->with($this->equalTo syntax.

EDIT 3

I read here about an undocumented parameter $canonicalize that orders arrays before comparing. When I use it like this:

$sut->expects($this->once())
    ->method('exposeAndSaveDataLines')
    ->with($this->equalTo($expectedLines, $delta = 0.0, $maxDepth = 10, $canonicalize = true, $ignoreCase = false),
        $this->anything(),
        $this->anything(),
        $this->anything(),
        $this->anything());

I see that the order of the arrays is indeed changed, but I still see the same error. Also, still one array is 'collapsed', which I suspect causes this failure. Besides, I don't want to order all my subarrays, they should be in the same order in the real and expected result.

--- Expected
+++ Actual
@@ @@
 Array (
     0 => Array (
         0 => Array (
             0 => ''
             1 => ''
             2 => '{C19D52BC-834C-45DA-B17F-74D73A2EC0BB}
             '
         )
         1 => Array (...)
     )

EDIT 4

When I use identicalTo instead of equalTo, I get a more elaborate error message, saying that the one array is not identical to the other array, while printing both of them. I copy-pasted them both into a text file, and used the command diff to check for any differences, but there were none. Still, PHPUnit claims that the two arrays are not equal/identical.

EDIT 5

When I use greaterThanOrEqual or even greaterThan instead of equalTo, then the test passes. This does not happen for lessThanOrEqual. This implies that there is a difference between the two arrays.

If I manually change the expected outcome into something with a string that is alphabetically before the correct string, I can lessThan pass as well, but then of course greaterThanOrEqual fails.

EDIT 6

I am getting convinced that the line ending of the strings in my array are making this comparison to fail, which doesn't show up in all comparisons.

I now have the following assertion.

public function test_actionUpload_v10MasterdataFile()
{
    ....
    $sut->expects($this->once())
        ->method('exposeAndSaveDataLines')
        ->will($this->returnCallback(function($lines) {
            $expectedLines = include ($this->dataDir . 'ExpectedLines.php');
            $arrays_similar = $this->similar_arrays($lines, $expectedLines);
            PHPUnit_Framework_Assert::assertTrue($arrays_similar);
        }));
    $sut->actionUpload();
}

private function similar_arrays($a, $b)
{
    if(is_array($a) && is_array($b))
    {
        if(count(array_diff(array_keys($a), array_keys($b))) > 0)
        {
            print_r(array_diff(array_keys($a), array_keys($b)));
            return false;
        }

        foreach($a as $k => $v)
        {
            if(!$this->similar_arrays($v, $b[$k]))
            {
                return false;
            }
        }

        return true;
    }
    else
    {
        if ($a !== $b)
        {
            print_r(PHP_EOL . 'A: '. $a. PHP_EOL . 'Type: ' . gettype($a) . PHP_EOL);
            print_r(PHP_EOL . 'B: '. $b. PHP_EOL . 'Type: ' . gettype($b) . PHP_EOL);
        }
        return $a === $b;
    }
}

With the following result.

A: {72C2F175-9F50-4C9C-AF82-9E3FB875EA82}

Type: string

B: {72C2F175-9F50-4C9C-AF82-9E3FB875EA82}

Type: string

 Answers

2

I finally got it to work, although it is a bit of a compromise. I am now removing newlines before I compare the arrays. This cannot be done in the with method, so I have made the following construction.

public function test_actionUpload_v10MasterdataFile()
{
    /*
     * Create a stub to disable the original constructor.
     * Exposing data and rendering are stubbed.
     * All other methods behave exactly the same as in the real Controller.
     */
    $sut = $this->getMockBuilder('MasterdataController')
                ->setMethods(array('exposeAndSaveDataLines', 'render'))
                ->disableOriginalConstructor()
                ->getMock();

    $sut->expects($this->once())
        ->method('exposeAndSaveDataLines')
        ->will($this->returnCallback(function($lines) {
            $expectedLines = include ($this->dataDir . 'ExpectedLines.php');
            PHPUnit_Framework_Assert::assertTrue($this->similar_arrays($lines, $expectedLines));
        }));

    // Execute the test
    $sut->actionUpload();
}

...

private function similar_arrays($a, $b)
{
    /**
     * Check if two arrays have equal keys and values associated with it, without
     * looking at order of elements, and discarding newlines.
     */
    if(is_array($a) && is_array($b))
    {
        if(count(array_diff(array_keys($a), array_keys($b))) > 0)
        {
            return false;
        }

        foreach($a as $k => $v)
        {
            if(!$this->similar_arrays($v, $b[$k]))
            {
                return false;
            }
        }
        return true;
    }
    else
    {
        $a = rtrim($a);
        $b = rtrim($b);
        $extended_output = false;
        if ($extended_output && ($a !== $b))
        {
            print_r(PHP_EOL . 'A: '. $a. PHP_EOL . 'Type: ' . gettype($a) . PHP_EOL);
            print_r(PHP_EOL . 'B: '. $b. PHP_EOL . 'Type: ' . gettype($b) . PHP_EOL);
        }
        return $a === $b;
    }
}
Sunday, October 16, 2022
5

You need to use call_user_func to do this:

call_user_func(array($this, $this->_auto));

Unfortunately PHP does not allow you to directly use property values as callables.

There is also a trick you could use to auto-invoke callables like this. I 'm not sure I would endorse it, but here it is. Add this implementation of __call to your class:

 public function __call($name, $args)
 {
     if (isset($this->$name) && is_callable($this->$name)) {
         return call_user_func_array($this->$name, $args);
     }
     else {
         throw new Exception("No such callable $name!");
     }
 }

This will allow you to invoke callables, so you can call free functions:

 $this->_auto = 'phpinfo';
 $this->_auto();

And class methods:

 $this->_auto = array($this, 'index');
 $this->_auto();

And of course you can customize this behavior by tweaking what __call invokes.

Saturday, December 17, 2022
 
2

You dont mock or stub methods in the Subject-under-Test (SUT). If you feel you have the need to mock or stub a method in the parent of the SUT, it likely means you shouldnt have used inheritance, but aggregation.

You mock dependencies of the Subject-under-Test. That means any other objects the SUT requires to do work.

Saturday, October 29, 2022
 
a_guest
 
2

Best answer here so far is I was doing it wrong. Service Providers run way before a controller is even loaded, and, when unit testing, Laravel's IlluminateFoundationTestingTestCase loads the application (calling all service providers' both boot and register methods) during execution of the setUp method, way before any calls can be made out during the execution of any individual test.

I tried finding a solution by moving the logic down and got something to work, something along the lines of:

class MyTestClass extends TestCase
{

    public function setUp()
    {
        // No call to parent::setUp()

        // From: IlluminateFoundationTestingTestCase
        $this->app = $this->createApplication();
        $this->client = $this->createClient();
        // Not this one!
        //$this->app->setRequestForConsoleEnvironment();
        $this->app->boot();

        // ...
    }

    public function testWhatever()
    {
        // Calls to this will now actually have a request object
        $this->call('GET', '/api/v1/courses');
    }
}

But that just can't be right, at least it doesn't feel so.

Instead, I figure it's probably best not to rely on anything in the Request object from within Service Providers. Instead, why not just inject an object that can do what I need it to do in the controller I want it to? No Service Provider necessary, and I can easily mock any object other than Request in Unit Tests. I should've believed the docs.

Update

Taking this answer to my own question a bit further, I believe my original mistake was that I was utilizing the Request object within a Service Provider. I think, on retrospection, that you should probably never use the Request object at all within a service provider, because providers get loaded for everything related to laravel (including artisan commands, which of course should have no request). My original code worked in the browser, but I probably would have noticed issues had I tried to run any artisan commands.

Thursday, August 18, 2022
3

It's a new feature. See here: http://msdn.microsoft.com/en-us/library/dd264739.aspx Named parameters are standard in ObjectiveC for instance. It takes some time to get used to them but they are a good thing. Only from looking you can tell what a parameter is meant for.

Sunday, December 4, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :