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

davy_yg's avatar
Level 27

PHP Unit for onlineshop

Hello,

I am in the middle of building an onlineshop. See, I only wonder what is the purpose of PHP Unit test for an onlineshop ?

ref: https://laracasts.com/discuss/channels/laravel/php-unit-testing-2

Don't you think it's much more efficient to test it manually? Since it takes more time to write the codes?

0 likes
9 replies
SilenceBringer's avatar

@davy_yg as far as your site will grows, it will require a lot of time to test on every update.

It's not enough to test obviously affected areas only, you need to test whole site always (as far as it contains a lot of dependencies, and your changes in 1 area may affect other areas too).

With the tests you may be sure your site works as expected (run whole test suite on every update to check whole site works as expected)

Also test can be used as documentation for other developers (as far as they describe expected result on sertain action)

davy_yg's avatar
Level 27

It looks like to me that php unit test can test the website functionality but not the appearance. Is that true?

For example whether my site looks good in mobile dan pc or in certain web browser.

Sinnbeck's avatar

@davy_yg Correct. Phpunit is for testing what controllers/methods return

To test if the site works well, you can use Lighthouse in chrome (F12->Lighthouse)

davy_yg's avatar
Level 27

So should I write a class test for each controller to test the whole web?

Sinnbeck's avatar

@davy_yg That is one way of testing yes. That is called Feature testing.

But you might have some complex function that needs to be tested with alot of different data (the cart for instance), so here a unit test might be a better fit.

1 like
davy_yg's avatar
Level 27

Okay, here is an example of a controller with 2 functions (index and store):

AdsController.php

	// First function		
	 public function index($type)
	 {
    //
    if($type != 'slider' && $type != 'featured_index' && $type != 'catalog_ad' && $type != 'banner_ads'){
        return back();
        }

    $ads = Advertisement::where('ad_type', $type)->latest()->get();    

    $count_slider = Advertisement::where('ad_type', 'slider')->count();
    $count_featured = Advertisement::where('ad_type', 'featured_index')->count();
    $count_banner = Advertisement::where('ad_type', 'banner_ads')->count();

    return view('admin.advertisement.ads', compact('ads', 'type', 'count_slider', 'count_featured', 'count_banner'));
    }


	// Second function		
    public function store(Request $request, $type)
    {

    if($type == 'slider')
        {
        $count = Advertisement::where('ad_type', 'slider')->count();
        
            if($count == 1){
                Session::flash('error', 'Cannot add slider more than 1');    
            return redirect()->back();
            }
                            
        }
    elseif($type == 'featured_index')
        {
        $count = Advertisement::where('ad_type', 'featured_index')->count();
        
            if($count == 3){
                Session::flash('error', 'Cannot add featured_index more than 3');    
            
            return redirect()->back();
            }
                            
        }
    elseif($type == 'banner_ads')
        {
        $count = Advertisement::where('ad_type', 'banner_ads')->count();
        
            if($count == 1){
                Session::flash('error', 'Cannot add banner_ads more than 1');    
            
            return redirect()->back();
            }
                            
        }

    if($type == "slider") {
         
         $this->validate($request, [
              'ad_title' => 'required',
              'ad_caption' => 'required',
              'ad_url' => 'required',
              'images' => 'required|dimensions:min_width=1360,min_height=345, max_width=1370,max_height=400'
         ]);   

         $dir_location = 'slider'; 
         $dir = 'ads/slider/';  

    }elseif($type == "featured_index") {

         $this->validate($request, [
              'ad_title' => 'required',
              'ad_caption' => 'required',
              'ad_url' => 'required',
              'images' => 'required|dimensions:min_width=400,min_height=225, max_width=415,max_height=230'
         ]); 

         $dir_location = 'featured_index';

         $dir = 'ads/featured_index/';  

    }elseif($type== "banner_ads") {

        $this->validate($request, [
              'ad_title' => 'required',
              'ad_caption' => 'required',
              'ad_url' => 'required',
              'images' => 'required|dimensions:min_width=1360,min_height=420, max_width=1370,max_height=425'
         ]); 

        $dir_location = 'banner_ads';
        $dir = 'ads/banner_ads/';
    }


    $ads = new Advertisement;

    $ads->ad_type = $type;
    $ads->ad_title = $request->ad_title;
    $ads->ad_caption = $request->ad_caption;
    $ads->ad_url = $request->ad_url;
        
        // Managing name

        $extension = $request->file('images')->extension();
        $name_wo_extension = pathinfo($request->file('images')->getClientOriginalName(), PATHINFO_FILENAME);

        // giving dash for each empty space in between name
        $slug = Str::slug($name_wo_extension, '-');

        $name = $slug . '.' . $extension;

        // check the existance of old file
        $count = count(Advertisement::where('ad_img_url', $dir.$name)->get());

        while($count != 0)
            {
                $slug = $slug . '-copy';
            }     


    $ads->ad_img_url = $dir . $name;

    Storage::putFileAs($dir_location, $request->file('images'), $name);

    $ads->save();

    Session::flash('flash', 'Ads type :'. $type . ' created');


    return redirect()->back();
    }

I only wonder what kind of test functions might fit to my ads controller ?

These are what I can think of:

  1. Testing if the page can be opened
  2. Testing input data like: slider, feature_index and banner_ads of the correct and incorrect size if it can be uploaded

I cannot think of what else do I need test using the php unit feature test ?

martinbean's avatar

Don't you think it's much more efficient to test it manually? Since it takes more time to write the codes?

@davy_yg A PHPUnit test can test a controller in literally milliseconds. How long does it take you? It would literally take you longer to just Alt + Tab to a browser window; PHPUnit will have already finished its test.

Add up all the time you spend manually testing stuff versus the mere seconds a PHPUnit test suite runs in, how much time is that wasted…?

PHPUnit tests are faster than any human, and repeatable.

davy_yg's avatar
Level 27

Okay this is the php unit test what I did from my controller above:

AdsTest.php

	<?php

	namespace Tests\Feature;

	use Illuminate\Foundation\Testing\RefreshDatabase;
	use Illuminate\Foundation\Testing\WithFaker;
	use Tests\TestCase;

	class AdsTest extends TestCase
	{				
		/**
 		* A basic feature test example.
 		*
 		* @return void
 		*/

		public function test_open_ads_slider_page()
		{

    		$type = 'slider';    

    		$response = $this->get('cpages/ads/'.$type);

    		$response->assertStatus(200);
		}

		public function test_open_ads_featuredindex_page()
		{

    		$type = 'featured_index';    

    		$response = $this->get('cpages/ads/'.$type);

    		$response->assertStatus(200);
		}

		public function test_open_ads_banner_ads_page()
		{

    		$type = 'banner_ads';    

    		$response = $this->get('cpages/ads/'.$type);

    		$response->assertStatus(200);
		}

		public function test_input_slider_data()
		{

    		$ads = new Advertisement;

    		$ads->ad_type = 'slider';
    		$ads->ad_title = 'slider 1';
    		$ads->ad_caption = 'slider 1';
    		$ads->ad_url = 'https://localhost/axe-backend-mg/public/home';
    		$ads->ad_img_url = '';

    		$this->assertTrue($ads->ad_img_url(''));

		}

		}

I still think something is missing. I am not sure about my last function and also if I need more functions to test the above controller ?

Tray2's avatar

@davy_yg You have done what is usually called a happy path test and that is all well and good. However you need to test the things that goes wrong as well.

If you create a blog and you are testing the store method these are some of the things you need to test.

  • A valid post is stored in the database
  • Only a logged in user can store a post
  • The title is required
    • The title must be at least five characters
    • The title must be unique
  • The body is required
    • The body must be at least one sentence
  • The author is required
    • The author must exist in the users table
  • The published at date is optional
    • The published at must be a date when present

These are just a few examples on what you need to test for just one method.

Here is an example from one of the applications that I develop and it's just the authorizations to see that all the routes has the correct middleware.

<?php

namespace Tests\Feature\Http;

use Tests\TestCase;

class AuthorizationTest extends TestCase
{
    public function protectedRoutesProvider()
    {
        return [
          'Artists: a guest is not authorized to visit the create page' => ['get', '/artists/create'],
          'Artists: a guest is not authorized to visit the edit page' => ['get', '/artists/1/edit'],
          'Artists: a guest is not authorized to visit the store page' => ['post', '/artists'],
          'Artists: a guest is not authorized to visit the update page' => ['put', '/artists/1'],
          'Artists: a guest is not authorized to visit the delete page' => ['delete', '/artists/1'],

          'Authors: a guest is not authorized to visit the create page' => ['get', '/authors/create'],
          'Authors: a guest is not authorized to visit the edit page' => ['get', '/authors/1/edit'],
          'Authors: a guest is not authorized to visit the store page' => ['post', '/authors'],
          'Authors: a guest is not authorized to visit the update page' => ['put', '/authors/1'],
          'Authors: a guest is not authorized to visit the delete page' => ['delete', '/authors/1'],

          'Books: a guest is not authorized to visit the create page' => ['get', '/books/create'],
          'Books: a guest is not authorized to visit the edit page' => ['get', '/books/1/edit'],
          'Books: a guest is not authorized to visit the store page' => ['post', '/books'],
          'Books: a guest is not authorized to visit the update page' => ['put', '/books/1'],
          'Books: a guest is not authorized to visit the delete page' => ['delete', '/books/1'],

          'Formats: a guest is not authorized to visit the create page' => ['get', '/formats/create'],
          'Formats: a guest is not authorized to visit the edit page' => ['get', '/formats/1/edit'],
          'Formats: a guest is not authorized to visit the store page' => ['post', '/formats'],
          'Formats: a guest is not authorized to visit the update page' => ['put', '/formats/1'],
          'Formats: a guest is not authorized to visit the delete page' => ['delete', '/formats/1'],

          'Genres: a guest is not authorized to visit the create page' => ['get', '/genres/create'],
          'Genres: a guest is not authorized to visit the edit page' => ['get', '/genres/1/edit'],
          'Genres: a guest is not authorized to visit the store page' => ['post', '/genres'],
          'Genres: a guest is not authorized to visit the update page' => ['put', '/genres/1'],
          'Genres: a guest is not authorized to visit the delete page' => ['delete', '/genres/1'],

          'Records: a guest is not authorized to visit the create page' => ['get', '/records/create'],
          'Records: a guest is not authorized to visit the edit page' => ['get', '/records/1/edit'],
          'Records: a guest is not authorized to visit the store page' => ['post', '/records'],
          'Records: a guest is not authorized to visit the update page' => ['put', '/records/1'],
          'Records: a guest is not authorized to visit the delete page' => ['delete', '/records/1'],

          'Tracks: a guest is not authorized to visit the create page' => ['get', '/tracks/create'],
          'Tracks: a guest is not authorized to visit the edit page' => ['get', '/tracks/1/edit'],
          'Tracks: a guest is not authorized to visit the store page' => ['post', '/tracks'],
          'Tracks: a guest is not authorized to visit the update page' => ['put', '/tracks/1'],
          'Trackss: a guest is not authorized to visit the delete page' => ['delete', '/tracks/1'],

          'BookCollection: a guest is not authorized to visit the store page' => ['post', '/bookcollections'],
          'BookCollection: a guest is not authorized to visit the delete page' => ['delete', '/bookcollections/1'],

          'BookRead: a guest is not authorized to visit the store page' => ['post', '/books/read'],
          'BookRead: a guest is not authorized to visit the delete page' => ['delete', '/books/read/1'],

          'UserPages: a guest is not authorized to visit the dashboard (home) page' => ['get', '/home']


        ];
    }

    /**
     * @test
     * @dataProvider protectedRoutesProvider
     * @param $method
     * @param $route
     */
    public function guests_are_redirected_to_login($method, $route)
    {
        $response = $this->$method($route);
        $response->assertLocation('/login');
    }
}

Please or to participate in this conversation.