Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

longestdrive's avatar

Learning Testing and mocking classes & Private Methods

I'm starting to learn testing - my laravel 5.5 app and gradually overcoming obstacles.

This is day 4 so a new challenge for me.

I'm trying to test a database record is created - I can do this and the tests work.

However the class that creates the record interacts with another class (a Cart class)- sends it some parameters and gets back a value.

I'm trying to isolate the test to this class so have tried to mock the class but I'm fairly sure I don't know what I'm doing here,

This is my test class:

class CreateBookingTest extends TestCase
{
    private $booking;

    public function setUp()
    {
        parent::setUp();
        $this->booking = \App::make(\App\Golfmanager\Booking\CreateBooking::class);

    }

    public function test_booking_created()
    {
        $attributes = $this->createAttributes();
        $user = \App\Models\User::find(1);
        $mock = \Mockery::mock(Cart::class);
          $mock->shouldReceive(['getCartGroupSize', 'instance'])
              ->with('greenfee')
              ->andReturn(1);

        $booking = $this->booking->handle($attributes, $user, 'greenfee');

        $this->assertEquals(1532, $booking->customer_id);
        $this->assertEquals(1, $booking->user_id);
        $this->assertEquals(1, $booking->group_size);

    }

    private function createAttributes()
    {
        $attributes = [
            'customer_id'=>1532,
            'play_date'=>'01/02/2018',
            'am_tee'=>'10:00 AM',
            'howBooked'=>'Drop In',
            'cartCount'=>1
        ];

        return $attributes;
    }
}

Here's the class I'm testing:

class CreateBooking
{
    /**
     * @var ReservationRepository
     */
    private $reservationRepository;
    /**
     * @var CustomerRepository
     */
    private $customerRepository;
    private $customer;
    private $groupSize = 0;
    private $costs = 0;
    private $saleTotal;
    private $booking;
    /**
     * @var Cart
     */
    private $cart;

    /**
     * CreateBooking constructor.
     * @param ReservationRepository $reservationRepository
     * @param CustomerRepository $customerRepository
     * @param Cart $cart
     */
    public function __construct(
        ReservationRepository $reservationRepository,
        CustomerRepository $customerRepository,
        Cart $cart
    )
    {
        $this->reservationRepository = $reservationRepository;
        $this->customerRepository = $customerRepository;
        $this->cart = $cart;
    }

    public function handle(Array $attributes, $user, $type = 'greefee')
    {

        $this->setUpBooking($attributes)
            ->createBooking($attributes)
            ->attachUser($user)
            ->attachCustomer();

        return $this->booking;
    }

    private function setUpBooking($attributes)
    {
        $this->setCustomer($attributes['customer_id']);
        $this->setGroupSize();
        $this->setCosts();
        $this->setSaleTotal();

        return $this;
    }

    private function setCustomer($customer_id)
    {
        $this->customer = $this->customerRepository->findById($customer_id);
    }

    private function setGroupSize()
    {
        $this->groupSize = $this->cart->getCartGroupSize('greenfee');
    }

//    Review: should this be here?
    private function setCosts()
    {
        $this->costs = $this->cart->getCartCostsTotal('greenfee');
    }

//    review: Should this be here?
    private function setSaleTotal()
    {
        $this->saleTotal = $this->cart->instance('greenfee')->total();
    }

    private function bookingAttributes($attributes)
    {
        $baseAttributes = array_only($attributes, ['play_date', 'am_tee', 'howBooked']);

        return array_merge($baseAttributes, $this->createTypeSpecificAttributes($attributes));
    }

    private function createTypeSpecificAttributes($attributes)
    {
        $typeSpecificAttributes = [
            'booked_date' => $attributes['play_date'],
            'group_size' => $this->groupSize,
            'sale_total' => $this->saleTotal,
            'costs_total' => $this->costs,
            'notes' => "Direct sale from `green fee\n",
            'status' => 'invoiced',
            'directSale' => 1,
            'event_type' => 'Golf',

        ];

        return $typeSpecificAttributes;
    }

    private function createBooking($attributes)
    {
        $this->booking = $this->reservationRepository->create($this->bookingAttributes($attributes));

        return $this;
    }

    private function attachCustomer()
    {
        $this->customer->reservation()->save($this->booking);

        return $this;

    }

    private function attachUser($user)
    {
        $user->reservation()->save($this->booking);

        return $this;
    }


}

I wanted to assert that first the class is called within the class I'm testing - makes use of the instance method and the getCartGroupSize method and returns a value

When running the test I can see 5 assertions but get one failure - because the value being returned by the mocked class doesn't appear to be used in the class I'm testing

Very confused how to correctly implement

I've tried moving the initiation of the class I'm testing after I've created the Mock but no change

ANy help appreciated

0 likes
1 reply
longestdrive's avatar

Ok - I think I've managed to successfully get the mock to be used by my class. I changed the test to the following:

public function test_booking_created()
    {

        $attributes = $this->createAttributes();

        $user = \App\Models\User::find(1);

        $mock = \Mockery::mock(Cart::class);

        $mock->shouldReceive('getCartGroupSize')
            ->with('greenfee')
            ->andReturn(1)
            ->shouldReceive('getCartCostsTotal')->with('greenfee')
            ->andReturn(10)
            ->shouldReceive('total')
            ->andReturn(10)
        ->shouldReceive('instance')->with('greenfee');

        $reservationRepository = \App::make(ReservationRepository::class);
        $customerRepository = \App::make(CustomerRepository::class);

        $this->booking = new CreateBooking($reservationRepository, $customerRepository, $mock);


        $booking = $this->booking->handle($attributes, $user, 'greenfee');

        $this->assertEquals(1532, $booking->customer_id);
        $this->assertEquals(1, $booking->user_id);
        $this->assertEquals(10, $booking->group_size);

    }

I initiated the dependencies for my class under test and passed those when initiating the class

My problem now is the mocked class has private methods which I understand will still run.

This one in particular is giving me problems:

private function setSaleTotal()
    {
        $this->saleTotal = $this->cart->instance('greenfee')->total();
    }

It calls on the methods within the parent class to grab the contents of the cart and then total them.

Now, because I've mocked the object there isn't actually any data there so I get an error Error : Call to a member function total() on null

What I thought I was doing was to check the call to total is made ->shouldReceive('total') and then get it to return a value '->andReturn(10)`

So I'm still very confused with mocking to avoid creating a real cart object and integrate that into my test (I've been working on the basis most unit tests should be isolated?)

Any help appreciated on my testing journey

Please or to participate in this conversation.