sunscreem
sunscreem

Reputation: 626

Laravel - How do I write a test for this?

Still pretty new to testing and can't work this out. How do I prevent Spatie's package from actually running here. I just want to fake a response from it.

Example Controller:

<?php

namespace App\Http\Controllers;
use Spatie\SslCertificate\SslCertificate;

class SSLController extends Controller {

 function getIsValid(){

     $certificate = SslCertificate::createForHostName('https://example.url');

     return $certificate->isValid();

    }
}

Example Test:

<?php 

namespace Tests\Unit;

use Tests\TestCase;
use Spatie\SslCertificate\SslCertificate;

class SSLCheckPlugInTest extends TestCase {

    public function it_returns_the_ssl_certs_status(){

         $this->mock(SslCertificate::class,function($mock){
            $mock->shouldReceive('createForHostName')->once();
        });

        $response = $this->get('/path-that-calls-the-controller-above');

    }
}

This example code still triggers the actual call in the package (to example.url) and returns:

Mockery\Exception\InvalidCountException: Method createForHostName(<Any Arguments>) from Mockery_2_Spatie_SslCertificate_SslCertificate should be called
 exactly 1 times but called 0 times.

I feel like I'm missing something really obvious.

edit

@bishop suggested writing a mock just for that one isValid method, but I can't work out how to do that.

Upvotes: 1

Views: 155

Answers (2)

D Malan
D Malan

Reputation: 11414

The $this->mock() method in a Laravel's tests cases is specifically meant for mocking an instance of an object in Laravel's service container. From the docs:

When mocking an object that is going to be injected into your application via Laravel's service container, you will need to bind your mocked instance into the container as an instance binding.

You can use Mockery directly instead to mock other classes or instances that won't be loaded into the service container.

In my opinion you should mock both the createForHostName and isValid methods. The purpose of your test would basically then be just to check that your controller method returns what it should given a known validity of a certificate.

You can first mock the SslCertificate instance that you want to return from createForHostName with something like this:

$certificateMock = Mockery::mock('overload:Spatie\SslCertificate\SslCertificate');
$certificateMock->shouldReceive('isValid')->once()->andReturn(True);

We're overloading the class above so that we can mock both instance methods and static methods of the class.

Then you can mock the createForHostName method to return an instance of the mocked class from above:

$certificateMock->shouldReceive('createForHostName')->once()->andReturn($certificateMock);

Upvotes: 1

Martin Bean
Martin Bean

Reputation: 39389

It’ll be difficult to mock SslCertificate because you’re using it statically.

Instead, you should inject it into the class (or method). That way, it’ll be resolved by Laravel’s service container, so you’ll be able to mock it.

class SSLController extends Controller
{
    function getIsValid(SslCertificate $sslCertificate)
    {
        $certificate = $sslCertificate->createForHostName('https://example.url');

        return $certificate->isValid();
    }
}

class SSLCheckPlugInTest extends TestCase
{
    public function it_returns_the_ssl_certs_status()
    {
        $this->mock(SslCertificate::class, function ($mock) {
            $mock->shouldReceive('createForHostName')->once();
        });

        $response = $this->get('/path-that-calls-the-controller-above');
    }
}

Now, when the controller is ran, it’ll check the container for SslCertificate and find your mock and execute the expectations.

Upvotes: 1

Related Questions