Bola
Bola

Reputation: 85

PHPunit method expected to be called 1 time, actually called 0 times

I've been stuck on this for a while and I'm not sure why PHPunit can't see that the function is being called.

This is the code I'm trying to test:

public function handle()
{
    $path = $this->request->getPath();
    $requestMethod = $this->request->getMethod();


    if (!$path) {
        $this->redirect('home');
    } else if (!$this->isMethodPathFound($path, $requestMethod)) {
        $this->redirect('404');
    } else {
        $handler = $this->getControllerFullName($this->routes[$path]['handler']);
        if (is_callable($handler)) {
            call_user_func($handler);
        } else {
            $this->redirect('404');
        }
    }
}

/**
 * @param string $path
 * @param int $statusCode
 */
public function redirect($path, $statusCode = 303)
{
    if (defined('TESTING_ENVIRONMENT') && TESTING_ENVIRONMENT) {
        return;
    }
    header(
        'Location: ' . $this->request->getProtocol() .
        $this->request->getHost() . '/' . $path,
        true,
        $statusCode
    );
    die();
}

The TESTING_ENVIRONMENT variable is set for the header function so it does not trigger on running PHPunit (I don't want to create another class to have that redirect function just to be able to mock it for one test) and this is the testing code:

public function testHandlePathIsEmpty()
{
    $requestMock = $this->getMockBuilder('\services\Request')->getMock();
    $requestMock->expects($this->once())->method('getPath')->willReturn('');
    $requestMock->expects($this->once())->method('getMethod')->willReturn('GET');
    $routerMock = $this->getMockBuilder('\services\Router')
        ->setConstructorArgs([$this->routes, $requestMock])
        ->enableProxyingToOriginalMethods()
        ->getMock();
    $routerMock->expects($this->once())->method('redirect')
        ->with('asdasd')->willReturn(true);
    $routerMock->handle();
}

The $routerMock object should definitely invoke the "redirect" function, and it says that it does not get invoked..even though when I var_dump/die inside the function, it does go inside of it.

Thanks for the help!

Upvotes: 1

Views: 5141

Answers (2)

Jojo
Jojo

Reputation: 2760

Though you hesitated to show the complete output of phpunit's error, your problem is very likely not that your method is not called, but that it is not called with all the expectations you defined.

Your code

$routerMock->expects($this->once())->method('redirect')
        ->with('asdasd')->willReturn(true);

translates to the following expectations: The method redirect must be called exactly once with an argument 'asdasd' and will return true.

From your testcode I do not see that there is asdasd passed to the redirect method. Your test will most likely succeed when you remove the with expectation.

Upvotes: 3

boesing
boesing

Reputation: 345

Just to make this clear. If you have to mock the class u want to test, your code is way to complex and you should think about implementing your logic in another way.

How about not mocking the class you are actually testing, create the new instance by passing the Request and a Router Mock (Router mock might not have any logic since you are not going to use it) and then do the following in your code:

public function handle()
{
    $request = $this->request;
    $path = $request->getPath();
    if (!$path) {
        $this->redirect('home');
    } else if (!$this->isMethodPathFound($path, $request->getMethod())) {
        $this->redirect('404');
    } else {
        $handler = $this->getControllerFullName($this->routes[$path]['handler']);
        if (is_callable($handler)) {
            call_user_func($handler);
        } else {
            $this->redirect('404');
        }
    }
}

In your Unit-Test, you now can just test for

$requestMock
    ->expects($this->never())
    ->method('getMethod');

I see that this would only cover the second case to not being executed but the third one could happen aswell. Thats always a point why your code is not clean enough. You should read something about KISS and SOLID to make your code more testable. This method is just too complex as you could test it correctly.

Upvotes: 0

Related Questions