Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing WordPress... without WordPress

Testing WordPress... without WordPress

One of the most known leitmotif for people into code quality assurance is: "run unit tests in isolation".
To apply this principle to code written for WordPress will bring us to write tests to be ran without loading WordPress.
The talk will mostly pivot on real-world examples of unit-testing WordPress plugins in isolation.

Giuseppe Mazzapica

December 16, 2017
Tweet

More Decks by Giuseppe Mazzapica

Other Decks in Programming

Transcript

  1. Testing WordPress…
    ...without WordPress
    Giuseppe Mazzapica

    View Slide

  2. Unit tests are a simple thing.
    Take the smallest portion of code
    that can be extracted from the rest
    of the application, execute it, and
    get feedback about the obtained
    result being the expected result.

    View Slide

  3. View Slide

  4. function ( , ){ = ( )[ ];
    it $m $p $d debug_backtrace 0 0
    ( ) and = ();
    is_callable $p $p $p
    global ; = ||! ;
    $e $e $e $p
    = .( ? : ). ;
    $o "\e[3" "2m✔" "1m✘" " It \e[0m"
    $p $m
    echo ? : { [ ]} { [ ]} ;
    $p " \n" " FAIL in: 'file' # 'line' \n"
    $o $o $d $d
    }
    (function(){global ; and die( );});
    register_shutdown_function $e $e 1
    #TestFrameworkInATweet
    280 characters version
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  5. require_once __DIR__. ;
    '/test-framework-in-a-tweet.php'
    ( , + === );
    it 'should sum two numbers.' 1 1 2
    ( , + === );
    it 'should display an X for a failing test.' 1 1 3
    ( , function () {
    it 'should append to an ArrayIterator.'
    = ();
    $iterator new ArrayIterator
    -> ( );
    $iterator append 'test'
    ( ) === ;
    return count $iterator 1
    } );
    $ php my-tests.php
    It should sum two numbers.

    ✘ It should display an X for a failing test. FAIL in: /path/to/my-tests.php #6
    ✔ It should append to an ArrayIterator.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  6. namespace MyCompany\MyPlugin;
    final class {
    Email
    private ;
    $email
    public function ( string ) {
    __construct $email
    if ( ! ( , ) ) {
    filter_var $email FILTER_VALIDATE_EMAIL
    ( { } );
    throw new \Exception " isn’t valid."
    $email
    }
    -> = ;
    $this email $email
    }
    public function (): string {
    __toString
    -> ;
    return $this email
    }
    public function ( Email ): bool {
    equals $email
    (string) === (string) ;
    return $this $email
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    View Slide

  7. require_once ;
    '/path/to/test-framework-in-a-tweet.php'
    require_once ;
    '/path/to/code/src/MyCompany/MyPlugin/Email.php'
    ( , function () {
    it 'should equal its string value.'
    = ( );
    $email new MyCompany\MyPlugin\Email '[email protected]'
    (string) === ;
    $email
    return '[email protected]'
    } );
    ( , function () {
    it 'should equal another instance by value.'
    = ( );
    $email new MyCompany\MyPlugin\Email '[email protected]'
    -> ( new ( ) );
    $email
    return equals Email '[email protected]'
    } );
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ php my-tests.php
    It should equal its string value.

    It should equal another instance by value.

    View Slide

  8. We tested our class with a
    tweet-sized function
    Our function is part of a
    WordPress plugin
    We tested our class without
    loading WordPress

    View Slide

  9. but what about...
    namespace MyCompany\MyPlugin;
    function () {
    register_product_cpt
    ( , [ ] );
    register_post_type 'product' /* bunch of args... */
    }
    1
    2
    3
    4
    5
    Can we test this function
    without loading WordPress?

    View Slide

  10. YES, WE CAN.
    but what about...
    namespace MyCompany\MyPlugin;
    function () {
    register_product_cpt
    ( , [ ] );
    register_post_type 'product' /* bunch of args... */
    }
    1
    2
    3
    4
    5
    Can we test this function
    without loading WordPress?

    View Slide

  11. require_once __DIR__. ;
    '/test-framework-in-a-tweet.php'
    require_once ;
    '/path/to/wordpress/wp-load.php' // YOLO
    it( , function () {
    'should register product post type.'
    = ( );
    $exists_before post_type_exists 'product'
    \ ();
    MyCompany\MyPlugin register_product_cpt
    = ( );
    $exists_after post_type_exists 'product'
    === false && = true;
    return $exists_before $exists_after
    } );
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    first, let’s try loading WP...

    View Slide

  12. register_post_type works
    post_type_exists works
    our function calls WordPress
    functions in the proper way
    What is that test testing?

    View Slide

  13. Our responsibility, when
    writing tests for a plugin, is to
    test code works.
    our
    The best way to do it, is to
    write tests assuming WP just
    works.

    View Slide

  14. function ( ) {
    post_type_exists $post_type
    ( { } , );
    return array_key_exists "post_type_ "
    $post_type $GLOBALS
    }
    function ( , ) {
    register_post_type $post_type $args
    [ { } ] = ;
    $GLOBALS "post_type_ "
    $post_type $args
    }
    it( , function () {
    'should register product post type.'
    = ( );
    $exists_before post_type_exists 'product'
    ();
    MyCompany\MyPlugin\register_product_cpt
    = ( );
    $exists_after post_type_exists 'product'
    === false && = true;
    return $exists_before $exists_after
    } );
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    View Slide

  15. Test code did not changed.
    But this time we didn’t tested
    WP code works, but we tested
    that our code calls WordPress
    functions in the proper way.
    And it was easy and fast.

    View Slide

  16. Because WordPress was not
    loaded, we were able to write
    a “fake”, simplified, version
    of WordPress functions, for
    the sole purpose of testing.
    That’s called .
    STUB

    View Slide

  17. It was .
    convenient
    Without loading WordPress we:
    avoided scaffolding step
    removed the burden of database
    and system configuration
    improved tests performance

    View Slide

  18. To run tests in the context of
    WordPress is important at
    some point to verify the
    correctness of the plugin.
    It is essential for plugins with
    heavy UI integration.

    View Slide

  19. What about objects?
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface {
    Clock
    public function (): int;
    hours
    public function (): int;
    minutes
    public function (): int;
    seconds
    }
    function ( Clock ) {
    maybeGong $clock
    if ( -> () === && -> () === ) {
    $clock $clock
    minutes seconds
    0 0
    = -> ();
    $hours $clock hours
    = > && <= ? : ( - );
    $gongs $h $h $h $h
    0 12 12
    abs
    ( ( , ) );
    print rtrim str_repeat 'GONG ' $gongs
    }
    }

    View Slide

  20. 3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    final class implements {
    SystemClock Clock
    public function (): int {
    hours
    (int) ( () )-> ( );
    return new \Datetime format 'G'
    }
    public function (): int {
    minutes
    (int) ( () )-> ( );
    return new \Datetime format 'i'
    }
    public function (): int {
    seconds
    (int) ( () )-> ( );
    return new \Datetime format 's'
    }
    }
    A stub will allow us to test this
    non-determistic behavior.

    View Slide

  21. 3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class implements {
    ClockStub Clock
    private ;
    $hours
    private ;
    $minutes
    public function ( int , int ) {
    __construct $hours $minutes
    -> = ;
    $this hours $hours
    -> = ;
    $this minutes $minutes
    }
    public function (): int {
    hours
    -> ;
    return $this hours
    }
    public function (): int {
    minutes
    -> ;
    return $this minutes
    }
    public function (): int {
    seconds
    ;
    return 0
    }
    }

    View Slide

  22. 3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    it( , function () {
    "should gong 3 times at 3 o'clock."
    ();
    ob_start
    ( ( , ) );
    maybeGong new ClockStub 3 0
    () === ;
    return ob_get_clean "GONG GONG GONG"
    } );
    ( , function () {
    it 'should not gong at 10 past 3.'
    ();
    ob_start
    ( ( , ) );
    maybeGong new ClockStub 3 10
    () === ;
    return ob_get_clean ""
    } );

    View Slide

  23. We leveraged polymorphism to write
    a stub object that is a different,
    deterministic, implementation of the
    same interface.
    We could do that because the
    maybe_gong() function was written
    to accept an interface as parameter.

    View Slide

  24. Stubs are great, but stubs
    are .
    hard
    Stubs can be written for
    .
    objects
    Stubs can be written for
    .
    functions

    View Slide

  25. Stubs are .
    code
    And code needs to be written.
    Code needs to be maintained.
    Code might have has bugs.

    View Slide

  26. Introducing
    mock objects.
    A sort of virtual stubs: they are not
    written as real classes in the
    filesystem, but created on runtime
    based on a set of expectations.

    View Slide

  27. 6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    it( , function () {
    "should gong 3 times at 3 o'clock."
    = :: ( );
    $threeOClock Mockery mock 'Clock'
    -> ( )-> ()
    $threeOClock shouldReceive once
    'hours'
    -> ()-> ( );
    withNoArguments andReturn 3
    -> ( )-> ()
    $threeOClock shouldReceive once
    'minutes'
    -> ()-> ( );
    withNoArguments andReturn 0
    -> ( )-> ()
    $threeOClock shouldReceive once
    'seconds'
    -> ()-> ( );
    withNoArguments andReturn 0
    ();
    ob_start
    ( );
    $threeOClock
    maybeGong
    = () === ;
    $result ob_get_clean "GONG GONG GONG"
    :: ();
    Mockery close
    ;
    $result
    return
    });

    View Slide

  28. Mock Objects
    respond to method calls in a
    pre-defined way (like stubs)
    ensure methods are called a
    specific number of times with
    specific type and number of args
    offer less surface to bugs, moving
    the complexity to a 3rd party lib

    View Slide

  29. 6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class {
    ProductsQuery
    private ;
    $query
    private = ;
    $paged 0
    public function ( = NULL ) {
    __construct WP_Query $query
    -> = ? : ();
    $this query $query new WP_Query
    }
    public function (): array {
    next_products
    -> ++;
    $this paged
    -> -> ( [
    $this query query
    => ,
    'post_type' 'product'
    => ,
    'posts_per_page' 10
    => -> ,
    $this
    'paged' paged
    ] );
    if ( ! -> -> () ) {
    $this query have_posts
    [];
    return
    }
    -> -> ;
    $this
    return query posts
    }

    View Slide

  30. 6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    it( , function () {
    "should get first 10 products."
    = ( function() {
    $ten_posts array_map
    :: ( );
    return Mockery mock 'WP_Post'
    }, ( , ) );
    range 0 9
    = :: ( );
    $query_mock Mockery mock 'WP_Query'
    $query_mock
    -> ( )-> ()
    shouldReceive once
    'query'
    -> ( function ( array ) {
    withArgs $args
    ( [ ] ?? ) === ;
    return $args 'post_type' '' 'product'
    } )
    -> ( , );
    andSet 'posts' $ten_posts
    $query_mock
    -> ( )-> ()
    shouldReceive once
    'have_posts'
    -> ()-> ( true );
    withNoArgs andReturn
    = ( );
    $query $query_mock
    new ProductsQuery
    -> () === ;
    return $query $ten_posts
    next_products
    } );

    View Slide

  31. When that is done, that fact that
    actual posts are pulled from DB is
    not a concern to test in our plugin.
    Thanks to Mockery we tested that
    WP_Query methods are called the
    right number of times, with the right
    arguments.

    View Slide

  32. Set expectations for functions
    Define functions multiple times
    What is necessary to obtain for
    functions the same power Mockery
    provides for objects?

    View Slide

  33. Set expectations for functions
    Define functions multiple times
    > Doable with a little bit of code
    > Doable thanks to Patchwork
    What is necessary to obtain for
    functions the same power Mockery
    provides for objects?

    View Slide

  34. Brain Monkey is a library that
    uses Patchwork to bring
    Mockery object expectations
    to functions.

    View Slide

  35. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    namespace bm;
    require_once __DIR__. ;
    '/vendor/antecedent/patchwork/Patchwork.php'
    require_once __DIR__. ;
    '/vendor/autoload.php'
    require_once __DIR__. ;
    '/test-framework-in-a-tweet.php'
    function ( , ) { = ( )[ ];
    it $m $p $d debug_backtrace 0 0
    try {
    ();
    \Brain\Monkey\setUp // before each test
    ();
    ob_start
    \ ( , );
    it $m $p // actual test
    = ();
    $output ob_get_clean
    ();
    \Brain\Monkey\tearDown // after each test
    echo ;
    $output
    } catch(\ ) {
    Throwable $t
    [ ] = true;
    $GLOBALS 'e'
    echo { [ ]} { [ ]} ;
    "\e[31m✘ It \e[0m FAIL in: 'file' # 'line' ."
    $m $d $d
    echo . -> () . ;
    ' ' "\n"
    $t getMessage
    }
    }
    Just a little of configuration

    View Slide

  36. 6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function ( ) {
    last_post_shortcode $args
    = [ => 1, => 0, => ];
    $def 'date' 'content' 'post_type' 'post'
    = ( , , );
    $atts $def
    shortcode_atts $args 'last_post'
    = ([
    $posts get_posts
    => [ ],
    $atts
    'post_type' 'post_type'
    => ,
    'posts_per_page' 1
    ]);
    if ( ) {
    $posts
    [ ] = ( );
    $GLOBALS $posts
    'post' reset
    ( [ ]);
    $GLOBALS
    setup_postdata 'post'
    = ( , ());
    $out sprintf '' get_the_ID
    if ( [ ]) {
    $atts 'date'
    = ( ());
    $date esc_html get_the_date
    .= ( , );
    $out $date
    sprintf '%s'
    }
    .= ( , ( ()));
    $out sprintf '%s' esc_html get_the_title
    [ ] and .= ();
    $atts $out
    'content' get_the_content
    ();
    wp_reset_postdata
    . ;
    $out
    return ''
    }
    }

    View Slide

  37. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    require_once __DIR__. ;
    '/test-framework-in-a-tweet-monkey.php'
    require_once ;
    '/path/to/src/MyCompany/MyPlugin/functions.php'
    bm\ ( , function () {
    it 'should render shortcode for last product.'
    = [ => true, => ];
    $args 'content' 'post_type' 'products'
    ( )
    Brain\Monkey\Functions\expect 'get_posts'
    -> ()
    once
    -> ( function( array ) {
    andReturnUsing $args
    if ( [ ] === ) {
    $args 'post_type' 'products'
    [ :: ( ) ];
    return Mockery mock 'WP_Post'
    }
    });
    ( )
    Brain\Monkey\Functions\expect 'shortcode_atts'
    -> ()
    once
    -> ( :: ( ), , )
    with type
    Mockery 'array' 'lastpost'
    $args
    -> ( function( , array ) {
    andReturnUsing $defaults $args
    ( , );
    return array_merge $defaults $args
    } );

    View Slide

  38. 1
    2
    3
    4
    5
    6
    ..
    23
    24
    25
    26
    27
    28
    29
    30
    31
    require_once __DIR__. ;
    '/testframeworkinatweet-brainmonkey.php'
    require_once ;
    '/path/to/src/MyCompany/MyPlugin/functions.php'
    bm\ ( , function () {
    it 'should render shortcode for last product.'
    ...
    ( )
    Brain\Monkey\Functions\expect 'setup_postdata'
    -> ()
    once
    -> ( :: ( ) );
    with Mockery type 'WP_Post'
    ( )
    Brain\Monkey\Functions\expect 'wp_reset_postdata'
    -> ()
    once
    -> ();
    withNoArgs

    View Slide

  39. 1
    2
    3
    4
    5
    6
    ..
    31
    32
    33
    34
    35
    36
    37
    38
    39
    require_once __DIR__. ;
    '/testframeworkinatweet-brainmonkey.php'
    require_once ;
    '/path/to/src/MyCompany/MyPlugin/functions.php'
    bm\ ( , function () {
    it 'should render shortcode for last product.'
    ...
    \ ( [
    Brain\Monkey\Functions stubs
    => ,
    'get_the_ID' 123
    => ,
    'get_the_date' '15/12/2017'
    => ,
    'get_the_title' 'Test Title'
    => ,
    'get_the_content' 'The content!'
    => null,
    'esc_html'
    ] );

    View Slide

  40. 1
    2
    3
    4
    5
    6
    7
    8
    ..
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    require_once __DIR__. ;
    '/testframeworkinatweet-brainmonkey.php'
    require_once ;
    '/path/to/src/MyCompany/MyPlugin/functions.php'
    bm\ ( , function () {
    it 'should render shortcode for last product.'
    = [ => true, => ];
    $args 'content' 'post_type' 'products'
    ...
    = ( );
    $rendered $args
    last_post_shortcode
    = ( , );
    $has_id $rendered
    strpos 'id="123"'
    = ( , );
    $has_date $rendered
    strpos '15/12/2017'
    = ( , );
    $has_title $rendered
    strpos 'Test Title'
    = ( , );
    $has_content $rendered
    strpos 'The content!'
    && && && ;
    return $has_id $has_date $has_title $has_content
    }

    View Slide

  41. 5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    bm\ ( , function () {
    it 'should render shortcode for last product.'
    = [ => TRUE, => ];
    $args 'content' 'post_type' 'products'
    \ ( )
    Brain\Monkey\Functions expect 'get_posts'
    -> ()
    once
    -> ( function ( array ) {
    andReturnUsing $args
    if ( [ ] === ) {
    $args 'post_type' 'products'
    [ \ :: ( ) ];
    return Mockery mock 'WP_Post'
    }
    } );
    \ ( )
    Brain\Monkey\Functions expect 'shortcode_atts'
    -> ()
    once
    -> ( \ :: ( ), \ :: ( ), )
    with type type
    Mockery Mockery
    'array' 'array' 'last_post'
    -> ( function ( , array ) {
    andReturnUsing $defaults $args
    ( , );
    return array_merge $defaults $args
    } );
    \ ( )
    Brain\Monkey\Functions expect 'setup_postdata'
    -> ()
    once
    -> ( :: ( ) );
    with type
    Mockery 'WP_Post'
    \ ( )
    Brain\Monkey\Functions expect 'wp_reset_postdata'
    -> ()
    once
    -> ();
    withNoArgs
    \ ( [
    Brain\Monkey\Functions stubs
    => ,
    'get_the_ID' 123
    => ,
    'get_the_date' '15/12/2017'
    => ,
    'get_the_title' 'Test Title'
    => null,
    'esc_html'
    => ,
    'get_the_content' 'The content!'
    ] );
    = ( );
    $rendered $args
    last_post_shortcode
    = ( , );
    $has_id $rendered
    strpos 'id="123"'
    = ( , );
    $has_date $rendered
    strpos '15/12/2017'
    = ( , );
    $has_title $rendered
    strpos 'Test Title'
    = ( , );
    $has_content $rendered
    strpos 'The content!'
    && && && ;
    $has_id $has_date $has_title $has_content
    return
    } );
    Quite a lot of code...
    but the limited features
    of our test framework
    do not help here...

    View Slide

  42. There is a set of functions that we
    find in each WordPress plugins,
    and mock them every time is quite
    tedious and awkward: plugin API.
    add_filter()
    apply_filters()
    add_action()
    do_action()
    current_filter()
    did_action()
    and others...

    View Slide

  43. Brain Monkey defines already
    almost all those functions in a
    way that is 100% compatible
    with WordPress real code.

    View Slide

  44. 6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    namespace MyPlugin;
    function () {
    create_initializer
    if ( ! ( ) ) {
    did_action 'init'
    null;
    return
    }
    ( );
    do_action 'my_plugin_init'
    = (
    $init_class apply_filters
    'my_init_class',
    Initializer::class
    );
    = ();
    $initializer $init_class
    new
    ( , [ , ] );
    add_action 'template_redirect' 'init'
    $initializer
    ;
    return $initializer
    }

    View Slide

  45. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    require_once __DIR__. ;
    '/testframeworkinatweet-brainmonkey.php'
    require_once ;
    '/path/to/src/MyPlugin/functions.php'
    bm\ ( , function () {
    it 'should not init if WP is not initialized.'
    () === null;
    return MyPlugin\create_initializer
    } );
    bm\ ( , function () {
    it 'should init if WP is initialized.'
    ( );
    do_action 'init'
    = ();
    $initializer MyPlugin\create_initializer
    instanceof ;
    return $initializer MyPlugin\Initializer
    } );

    View Slide

  46. Thanks to Brain Monkey we ran
    our tests just like WordPress
    was loaded, without any mock
    or stub, and everything worked
    as expected.
    But there’s more...

    View Slide

  47. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    bm\ (
    it
    ,
    'should not fire my_plugin_init if WP is not initialized.'
    function () {
    \ ( )
    'my_plugin_init'
    Brain\Monkey\Actions expectDone
    -> ();
    never
    ();
    MyPlugin\create_initializer
    TRUE;
    return
    }
    );

    View Slide

  48. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    bm\ (
    it
    ,
    'should fire my_plugin_init once if WP is initialized.'
    function () {
    ( )
    'my_plugin_init'
    Brain\Monkey\Actions\expectDone
    -> ()
    once
    -> ();
    withNoArgs
    ( );
    'init'
    do_action
    ();
    MyPlugin\create_initializer
    TRUE;
    return
    }
    );

    View Slide

  49. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    bm\ (
    it
    ,
    'should add hook initializer method to template redirect'
    function () {
    ( )
    'template_redirect'
    Brain\Actions\expectAdded
    -> ()
    once
    -> ( function ( array ) {
    withArgs $cb
    return
    ( [ ], ::class )
    is_a $cb 0 MyPlugin\Initializer
    && [ ] === ;
    'init'
    $cb 1
    } );
    ( );
    'init'
    do_action
    ();
    MyPlugin\create_initializer
    true;
    return
    }
    );

    View Slide

  50. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    bm\ (
    it
    ,
    'should use a different initializer class if filtered.'
    function () {
    = :: ( ::class );
    $mock Mockery MyPlugin\Initializer
    mock
    = ( );
    $mock_class $mock
    get_class
    ( )
    'my_plugin_init_class'
    Filters\expectApplied
    -> ()
    once
    -> ( ::class )
    with MyPlugin\Initializer
    -> ( );
    andReturn $mock_class
    ( );
    'init'
    do_action
    return ( (), );
    is_a MyPlugin\create_initializer $mock_class
    }
    );

    View Slide

  51. Brain Monkey allows to test
    plugin API functions with the
    finest level of control, and
    with a syntax that is readable
    and consistent with the one
    used to create mock objects
    with Mockery.

    View Slide

  52. Brain Monkey defines following
    logicless functions:
    __return_true()
    __return_false()
    __return_null()
    __return_empty_array()
    __return_empty_string()
    __return_zero()
    trailingslashit()
    untrailingslashit()

    View Slide

  53. Thanks to Mockery and
    Brain Monkey we have the
    possibility to write unit tests
    for plugins without loading
    WordPress in a way that is
    very easy to start with and
    produces maintainable tests.

    View Slide

  54. Unit tests are a simple concept
    Do not load WP for plugins unit
    tests is often convenient
    Mockery and Brain Monkey can
    help with that.
    Takeaways:

    View Slide

  55. Giuseppe Mazzapica
    Biggest German WordPress Agency
    WordPress.com
    Featured Service Partner
    Gold Certified
    WooCommerce Expert
    https://gmazzap.me
    @gmazzap
    github.com/gmazzap
    UESTIONS
    Q ?

    View Slide