Testing WordPress

COQUARD Cyrille

24/04/2024

What is testing?

COQUARD Cyrille

  • Stands for automated tests
  • Code to assert application behavior
  • Multiple type of tests

24/04/2024

Effective testing is hard

COQUARD Cyrille

  • Know why testing
  • Know what to test
  • Know how to test
  • Know when to test

Bad test

=

Useless

24/04/2024

Effective testing is high reward

COQUARD Cyrille

Tests are your copilot

  • Low number of bugs
  • Delivering on time
  • Prevent over engineering

24/04/2024

Unit

vs

Integration

COQUARD Cyrille

24/04/2024

Unit vs Integration:

The academic way

COQUARD Cyrille

Unit

  • Class or method level
  • More isolated
  • Developper oriented
  • Easy to setup
  • First ones to learn

Integration

  • Feature level
  • More abstract
  • Business oriented
  • Complex to setup

24/04/2024

Unit vs Integration:

The academic way

COQUARD Cyrille

What is the issue?

24/04/2024

Unit vs Integration:

The academic way

COQUARD Cyrille

We ain't at school

  • CEO / Manager decides
  • Rentability / on schedule is key

24/04/2024

Unit vs Integration:

The real world way

COQUARD Cyrille

Unit

  • More fragile so low productivity
  • Developper language
  • Lot of tests
  • Need to create testcases

Integration

  • More abstract so high productivity
  • Business language
  • Reduced number of tests
  • Based on project definition

24/04/2024

Unit vs Integration:

Start with integration

COQUARD Cyrille

Integration

  • Higher productivity
  • Business language (transparency)
  • Reduced number of tests
  • Ensure the feature is done

24/04/2024

Unit vs Integration:

Start with integration

COQUARD Cyrille

Unit

  • Integration test not possible
  • Still provides coverage
  • Ensure code is working
  • Can't ensure the feature is done

24/04/2024

How to test

COQUARD Cyrille

24/04/2024

How to test:

Setup the environnement

COQUARD Cyrille

  • Setup a development env: wordpress/env
  • Setup integration tests:  wp-media/phpunit
  • Ease filter mocking: wp-launchpad/phpunit-wp-hooks
  • Create the base for integration tests

24/04/2024

How to test:

Create tests

COQUARD Cyrille

Now what?

24/04/2024

How to test:

What to test?

COQUARD Cyrille

Assert you made the job not work done

  • Testing your code: Useless
  • Testing expectations: Useful

24/04/2024

How to test:

What to test ?

COQUARD Cyrille

Test Driven Development

  • Really trendy
  • Really dogmatic
  • Better done than perfect

24/04/2024

How to test:

What to test ?

COQUARD Cyrille

Rome didn't built in one day

  • Learn gradually
  • TDD doctrines won't be followed strictly
  • You need to understand

24/04/2024

How to test:

What to test ?

COQUARD Cyrille

Most important in TDD

  • Write test before code: Keep candid mind
  • Make sure they fail before writing code: Useful code
  • Make sure all tests pass when writing code: Assert task done

24/04/2024

How to test:

What to test ?

COQUARD Cyrille

The utopy

  • Nice on paper
  • Issue is apply IRL

Issues

  • How to select the simplest test?
  • What is the quickest code?

24/04/2024

How to test:

Select simple test

COQUARD Cyrille

Acceptance criteria

  • Simple sentence asserting a behavior
  • Done by the person scoping the project
  • Ensure you are not testing your code

24/04/2024

How to test:

Select simple test

COQUARD Cyrille

What it is not

  • A video
  • A block of text
  • Code
- Image is preloaded using the image URLs 
from srcset attribute and becomes imagesrcset

- sizes="50vw" attribute is moved to the 
preload markup and becomes  imagesizes="50vw"

- fetchpriority="high" is added to the 
preload markup

- Image is excluded from LL feature

24/04/2024

How to test:

Gerkhin

COQUARD Cyrille

AAA

  • Arrange=Given
  • Act=When
  • Assert=Then
GIVEN image with srcset "http://my-image.example/img.png" 
WHEN the image is preloaded 
THEN image have imagesrcset "http://my-image.example/img.png" 
AND no srcset 

GIVEN image with sizes "50vw" 
WHEN the image is preloaded 
THEN image have imagesizes "50vw" 
AND no sizes

GIVEN an image 
WHEN the image is preloaded 
THEN image have fetchpriority "high"

GIVEN an image 
WHEN the image is preloaded 
THEN image is excluded from lazyload

24/04/2024

How to test:

Build around Acceptance criteria

COQUARD Cyrille

Find limits

  • Failing HTTP request
  • Issue while inserting DB
GIVEN image with srcset "http://my-image.example/img.png" 
WHEN the image is preloaded 
THEN image have imagesrcset "http://my-image.example/img.png" 
AND no srcset 

GIVEN image with sizes "50vw" 
WHEN the image is preloaded 
THEN image have imagesizes "50vw" 
AND no sizes

GIVEN an image 
WHEN the image is preloaded 
THEN image have fetchpriority "high"

GIVEN an image 
WHEN the image is preloaded 
THEN image is excluded from lazyload

24/04/2024

How to test:

Structure a test

COQUARD Cyrille

AAA

  • Arrange: put the plugin in a certain state
  • Act: run the logic to test
  • Assert: verify the plugin is in the expected state
<?php 

class Test_Logout extends TestCase {
    public function testLogoutShouldNotEdit()
    {
    	do_action(
        'grant_admin',
        wp_current_user()
        );
        
    	do_action('logout');
        
        $this->assertSame(
        	false,
        	apply_filters(
            'user_can_edit',
            false
            )
        );
    
    }
}

24/04/2024

How to test:

Find the quickest code

COQUARD Cyrille

Cheating is ok

  • You are testing on fixed data
  • What matters is the final output

Make baby steps

  • Checking one condition
  • You can edit previous tests

24/04/2024

Usual testing scenarios

COQUARD Cyrille

24/04/2024

How to test:

Adapt to usual cases

COQUARD Cyrille

Scenarios

  • Unleash CQRS power
  • Control filter value
  • Mock external API
  • Isolate callback

24/04/2024

How to test:

Unleash CQRS power

COQUARD Cyrille

Problem

Need to re-implement a way to interact with the element for each test

Solution

Use action and filter to cut your logic into small units

24/04/2024

How to test:

Unleash CQRS power

COQUARD Cyrille

Solution

Use action and filter to cut your logic into small units

<?php 

class Test_Logout extends TestCase {
    public function testLogoutShouldNotEdit()
    {
    	do_action(
        'grant_admin',
        wp_current_user()
        );
        
    	do_action('logout');
        
        $this->assertSame(
        	false,
        	apply_filters(
            'user_can_edit',
            false
            )
        );
    
    }
}

24/04/2024

How to test:

Control filter value

COQUARD Cyrille

Problem

Need control flow to direct the test into a certain state

Solution

Mock the filter with a callback function

24/04/2024

How to test:

Control filter value

COQUARD Cyrille

Solution

Mock the filter with a callback function

<?php 

class Test_Logout extends TestCase {
    public function testLogoutShouldNotEdit()
    {
      ...
    }
    
    /**
    * @hook my_filter 10
    */
    public function my_callback() {
    	return true;
    }
}

24/04/2024

How to test:

Mock external API

COQUARD Cyrille

Problem

The code call an external API and there is no control about it

Solution

Use wp_remote_request and mock the filter with a callback function

24/04/2024

How to test:

Mock external API

COQUARD Cyrille

Solution

Use wp_remote_request and mock the filter with a callback function

<?php 

class Test_Logout extends TestCase {
    public function testLogoutShouldNotEdit()
    {
      ...
    }
    
    /**
    * @hook pre_http_request 10
    */
    public function my_callback($response, $args, $url) {
   
   		if ( 
        	strpos(
            	$url,
                'https://app.imagify.io' ) === false
            ) {
			return $response;
		}
   
    	return [
          'body' => $message,
          'response' => ['code' => 200 ]
        ];
    }
}

24/04/2024

How to test:

Isolate callback

COQUARD Cyrille

Problem

The callback to test is linked to an hook with lot of callbacks making lot of noise

Solution

Isolate the callback by removing all other callbacks

24/04/2024

How to test:

Isolate callback

COQUARD Cyrille

Solution

Isolate the callback by removing all other callbacks

<?php 

class Test_Logout extends TestCase {
    /**
    * @isolate-hook my-event myCallback 15
    **/
    public function testLogoutShouldNotEdit()
    {
    	// ...
    }
}

24/04/2024

Any question?

COQUARD Cyrille

24/04/2024

Time for an example

COQUARD Cyrille

24/04/2024

Time for an example

The context

COQUARD Cyrille

Objectives

  • Add result to API
  • Save into DB
  • Add a job to queue

24/04/2024

Time for an example

The context

COQUARD Cyrille

Objectives

  • Fetch result from API
  • Save into DB

24/04/2024

Time for an example

The context

COQUARD Cyrille

Acceptance criterion

  • GIVEN no tracking row WHEN job added to Sauron THEN tracking job should be pending AND a job should be created to check result

  • GIVEN tracking row WHEN job added to Sauron THEN tracking job should be updated to pending AND a job should be created to check result

24/04/2024

Time for an example

The context

COQUARD Cyrille

Acceptance criterion

  • GIVEN no tracking row WHEN job fails to be added to Sauron THEN tracking job should be failed AND no job should be created to check result

24/04/2024

Time for an example

The context

COQUARD Cyrille

Complete cases:

  • Fail add API:

    • 5xx

    • 4xx

  • Fail add db

24/04/2024

Time for an example

The context

COQUARD Cyrille

Acceptance criterion

  • GIVEN no tracking row and no job WHEN job fetched from Sauron THEN tracking job should be failed 

  • GIVEN tracking row and no job WHEN job fetch from Sauron THEN tracking job should be updated to failed

24/04/2024

Time for an example

The context

COQUARD Cyrille

Acceptance criterion

  • GIVEN no tracking row and job pending WHEN job fetched from Sauron THEN tracking job should be pending and a new job should be created 

24/04/2024

Time for practice

COQUARD Cyrille

24/04/2024

Testing WordPress

By Cyrille Coquard

Testing WordPress

  • 113