Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 1910

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 62k
Author
Asked: November 26, 20242024-11-26T12:32:07+00:00 2024-11-26T12:32:07+00:00

How to test protected functions with PHPUnit in your Laravel app

  • 62k

An essential part of developing an app is testing your code. My preferred method of testing my Laravel code is using PHPUnit and this is how I use reflection to test those 'harder to reach' protected and private methods inside classes.

Typical places this arises

Building an application in PHP with Laravel means that we can call on a great number of classes to do various parts of the overall process we wish to achieve. For the purposes of this example (and where I first came across this issue) we will look at writing tests for a Job Class. A job class is one which will be sent some data and then process that data in the background via a worker so that the application is free to continue processing other commands.

A job class will often contain methods and variables that are;

  • public
  • private
  • protected

As you can imagine, public methods are easy enough to test against as they are “exposed” to the outside of the class instance. It gets a little more difficult to gain access to these private and protected methods for testing, as they are intended to be called only by other methods within the class instance. They'll be things like getters and setters so that a handle() function does not get clogged up with logic for retrieving it's necessary data.

When trying to run a test against such a class with private and protected methods, we will run into errors that look like the following two examples.

Error: Call to private SomeExampleClass::__construct() from scope TestsFeatureSomeExampleClassTest

What this error message is telling us is that we are trying to invoke a __construct() method inside the SomeExampleClass, which is not allowed from the scope of our test that may look something like the following:

namespace TestsFeatureSome;  class ExampleClassTest extends TestCase {     /** @test */     public function my_job_does_something()     {         $job = new SomeExampleClass($input);          // Make some assertions         ...     } } 
Enter fullscreen mode Exit fullscreen mode

Just in that single line, we have already encountered the problem. If we look at the example class itself, we'll see what trips us up:

<?php  declare(strict_types=1);  namespace Some;  final class ExampleClass {     private function __construct(         public readonly int $input,     ) {     }     ... } 
Enter fullscreen mode Exit fullscreen mode

Our attention should be drawn to the private function part. That constructor called on initiation CANNOT be called from outside of the namespace Some, we are trying to call it from TestsFeatureSomeExampleClassTest.

Error: Call to protected method SomeExampleClass::exampleMethod() from scope TestsFeatureSomeExampleClassTest

This error is very similar to the above, just in this case we may have a public function __construct() so our class may be instantiated from outside, but our helper functions cannot be invoked outside of the handle() method. If we use the same example as above, we can get one step further before hitting a wall:

namespace TestsFeatureSome;  class ExampleClassTest extends TestCase {     /** @test */     public function my_job_does_something()     {         $job = new SomeExampleClass($input);          $result = $job->exampleMethod();          // Make some assertions like...         // $result->assertEquals('expected', $result);         ...     } } 
Enter fullscreen mode Exit fullscreen mode

So the class is created with no problem, but we want to test methods that are called from within the handle() method so we can be certain that the data being handled is the correct data.

Solution: ReflectionClass

Available in; PHP 5, PHP 7, PHP 8

The Reflection API in PHP is a way to retrieve any and all information from a class during runtime. The way we can use it in this case is to effectively make an instantiated “copy” of the real class we wish to test, “grab” the method we wish to test from the reflection of the class, and then tweak it ever so slightly to make the private function public so that we may use it from outside of the class which in this case, is from out test.

Sounds simple enough…

new ReflectionClass($exampleClass)

This first step is to create the reflection class on which we can make our needed modifications. There is actually a small pre-first step to take and that is to make a new instance of the class we wish to reflect and save it to a variable which we then use in our call to create a new ReflecionClass().

         $exampleClass = new ExampleClass($input);          $reflection = new ReflectionClass($exampleClass);  
Enter fullscreen mode Exit fullscreen mode

Now we have something to work with that is a little more malleable and we can decide next what we want from this reflection. Based on the previous examples of errors, let's look at getting:

  • the __construct()
  • or some named method like exampleMethod()

getConstructor()

To get the constructor (public or private) so that we can feed it with whatever values we need to test against, we have this handy function which may be used as such to set the __construct() of the class as a useable variable:

         $exampleClass = new ExampleClass($input);          $reflection = new ReflectionClass($exampleClass);         $constructor = $reflection->getConstructor();  
Enter fullscreen mode Exit fullscreen mode

getMethod()

To get the protected method that we wish to test against we have this method that can be used in a very similar way to the above:

         $exampleClass = new ExampleClass($input);          $reflection = new ReflectionClass($exampleClass);         $method = $reflection->getMethod('exampleMethod');  
Enter fullscreen mode Exit fullscreen mode

setAccessible()

With both the the get helpers shown, all we are doing is saving the methods as they are to a variable. They haven't yet been unlocked for us to use as we please, but this is where this method steps in and shows the real benefit of this whole process. As easily as this, we can change the methods accessibility so that future calls to it from our non-matching namespace do not set off any errors.

         $exampleClass = new ExampleClass($input);          $reflection = new ReflectionClass($exampleClass);         $method = $reflection->getMethod('exampleMethod');         $method->setAccessible(true);  
Enter fullscreen mode Exit fullscreen mode

invokeArgs()

Now that we have a useable version of the protected/private method we wish to test the output of, we can call upon that method using this function which also feeds the method the needed parameters (arguments). Similarly to the setAccessible() we chain it on and it expects two arguments:

  1. The object to invoke upon. (null can be used here in the case of static methods)
  2. An array of arguments that the method to be invoked expects.
         $exampleClass = new ExampleClass($input);          $reflection = new ReflectionClass($exampleClass);         $method = $reflection->getMethod('exampleMethod');         $method->setAccessible(true);          $result = $method->invokeArgs($exampleClass, [$arg1, $arg2, ...]);          // Make assertions against the resulting output like...         // $this->assertEquals('expected result', $result);  
Enter fullscreen mode Exit fullscreen mode

Run your tests

That's it! After just a few extra lines of code, you are now able to make assertions in your PHPUnit test suites against otherwise inaccessible functions and you can sleep a little easier tonight knowing that the deepest trenches of your application are fully covered and there will not be any unwelcome bugs!

… at least not from the parts you wrote proper tests for.

laravelphptestingwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 2k
  • Popular
  • Answers
  • Author

    ES6 - A beginners guide - Template Literals

    • 0 Answers
  • Author

    Understanding Higher Order Functions in JavaScript.

    • 0 Answers
  • Author

    Build a custom video chat app with Daily and Vue.js

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.