A workshop with Thorsten Frommen,
Carl Alexander, and Giuseppe Mazzapica.
Either clone the repository...
git clone git@github.com:tfrommen/wceu-2018-unit-testing.git
...or download the ZIP file.
For the PHP part, install via Composer:
composer install
The process consisting of all life cycle activities, both static and dynamic, concerned with planning, preparation and evaluation of software products and related work products to determine that they satisfy specified requirements, to demonstrate that they are fit for purpose and to detect defects. ISTQB®
There are several things you might be able to test:
Also component testing or module testing.
The process of evaluating a system or component during or at the end of the development process to determine whether it satisfies specified requirements. IEEE Std 610.12-1990
Unit testing is applying white-box testing techniques,
meaning close examination of procedural level of detail.
Designing tests requires full knowledge of internals.
function get_answer(): string {
return '42';
}
To test if a function behaves as expected, one has to know what to expect.
Possible units in code:
Logic is testable, and so are functions and classes/modules.
Going even higher to namespaces, packages and so on is no good.
Define a unit as either logic, or function, or class/module.
function doubleOrHalf( number ) {
if ( number > 0 ) {
return number * 2;
}
return number / 2;
}
Logic very often not testable as individual unit.
⇒ A unit is either a function, or a class/module.
Important! Often overlooked.
or
function test_register_taxonomy() {
$tax = rand_str();
$this->assertFalse( taxonomy_exists( $tax ) );
register_taxonomy( $tax, 'post' );
$this->assertTrue( taxonomy_exists( $tax ) );
$this->assertFalse( is_taxonomy_hierarchical( $tax ) );
unset( $GLOBALS['wp_taxonomies'][ $tax ] );
}
This is not a unit test. But it has every right to exist.
class WhatClassTest extends \PHPUnit\Framework\TestCase {
public function test_what_method_should_do_what() {
$expected = 'What is the expected result?';
$actual = 'What is the actual result?';
static::assertSame( $expected, $actual, 'What should it do? (optional)' );
}
}
describe( 'What are you testing?', () => {
test( 'What should it do?', () => {
const expected = 'What is the expected result?';
const actual = 'What is the actual result?';
expect( actual ).toBe( expected );
} );
} );
For PHP:
./vendor/bin/phpunit --testsuite exercise1
For JavaScript:
./node_modules/.bin/jest exercise1
tests/php/exercise1/Exercise1Test.php
public function test_filter_add_error_codes() {
$error_codes = filter_add_error_code( [] );
static::assertTrue( in_array( 'workshop', $error_codes ), 'Added "workshop" error code.' );
}
function filter_add_error_code( array $error_codes ) {
$error_codes[] = 'workshops'; // <- This should be "workshop".
return $error_codes;
}
function filter_add_error_code( array $error_codes ) {
$error_codes[] = 'workshop';
return $error_codes;
}
tests/js/exercise1/LoremIpsum.test.js
describe( 'LoremIpsum', () => {
test( 'should have expected name', () => {
const { name } = LoremIpsum;
expect( name ).toBe( 'unit-testing-workshop/lorem-ipsum' );
} );
} );
// ...
const name = 'unit-testing-workshop/lorem-ipsmu'; // <- This should be "lorem-ipsum".
// ...
export default {
name,
settings,
};
// ...
const name = 'unit-testing-workshop/lorem-ipsum';
// ...
export default {
name,
settings,
};
For PHP:
./vendor/bin/phpunit --testsuite exercise2
For JavaScript:
./node_modules/.bin/jest exercise2
tests/php/exercise2/Exercise2Test.php
public function test_disable_plugin_updates_removes_update_plugins_cap() {
$caps = [
'update_plugins' => true,
];
$caps = filter_disable_plugin_updates( $caps );
// Add missing assertion here.
}
function filter_disable_plugin_updates( array $allcaps ) {
unset( $allcaps['install_plugins'] );
unset( $allcaps['update_plugins'] );
return $allcaps;
}
public function test_disable_plugin_updates_removes_update_plugins_cap() {
$caps = [
'update_plugins' => true,
];
$caps = filter_disable_plugin_updates( $caps );
static::assertArrayNotHasKey( 'update_plugins', $caps );
}
tests/js/exercise2/array.test.js
describe( 'mapObjectsToProperty', () => {
test( 'should return string as is', () => {
const data = 'Some data here...';
const propertyName = 'children';
const actual = mapObjectsToProperty( data, propertyName );
// Fill in the expected value.
expect( actual ).toBe( /* TODO */ );
} );
} );
export function mapObjectsToProperty( data, propertyName ) {
if ( ! Array.isArray( data ) ) {
return data;
}
return data.map( ( item ) => (
typeof item === 'object' && propertyName in item
? item[ propertyName ]
: item
) );
}
describe( 'mapObjectsToProperty', () => {
test( 'should return string as is', () => {
const data = 'Some data here...';
const propertyName = 'children';
const actual = mapObjectsToProperty( data, propertyName );
expect( actual ).toBe( data );
} );
} );
Define dummy functions and objects that you have absolute control over.
Take care of two things:
For PHP:
./vendor/bin/phpunit --testsuite exercise3
For JavaScript:
./node_modules/.bin/jest exercise3
tests/php/exercise3/Exercise3Test.php
public function test_delete_member_page_for_no_user_team_member_page() {
Monkey\Functions\when( 'get_userdata' )->justReturn( true );
static::assertSame( 1, delete_member_page( 42 ) );
}
function delete_member_page( $user_id ) {
if ( ! get_userdata( $user_id ) ) {
return 0;
}
$user_page = get_user_team_member_page( $user_id );
if ( ! $user_page ) {
return 1;
}
if ( ! wp_delete_post( $user_page->ID ) ) {
return -1;
}
return 2;
}
public function test_delete_member_page_for_no_user_team_member_page() {
Monkey\Functions\when( 'get_userdata' )->justReturn( true );
Monkey\Functions\when( 'get_user_team_member_page' )->justReturn( null );
static::assertSame( 1, delete_member_page( 42 ) );
}
tests/js/exercise3/index.test.js
jest.mock( '../../js/src/blocks/LoremIpsum', () => ( {
name: 'lorem-ipsum',
settings: { lorem: 'ipsum' },
} ) );
describe( 'index', () => {
test( 'should register LoremIpsum block', () => {
const { registerBlockType } = global.wp.blocks;
// Fill in the missing value(s)/variable(s).
expect( registerBlockType ).toHaveBeenCalledWith( /* TODO, TODO */ );
} );
} );
import LoremIpsum from './blocks/LoremIpsum';
import Progress from './blocks/Progress';
const { registerBlockType } = wp.blocks;
[
LoremIpsum,
Progress,
].forEach( ( { name, settings } ) => {
registerBlockType( name, settings );
} );
jest.mock( '../../js/src/blocks/LoremIpsum', () => ( {
name: 'lorem-ipsum',
settings: { lorem: 'ipsum' },
} ) );
describe( 'index', () => {
test( 'should register LoremIpsum block', () => {
const { registerBlockType } = global.wp.blocks;
expect( registerBlockType ).toHaveBeenCalledWith( 'lorem-ipsum', { lorem: 'ipsum' } );
} );
} );
For PHP:
./vendor/bin/phpunit --testsuite exercise4
For JavaScript:
./node_modules/.bin/jest exercise4
tests/php/exercise4/Exercise4Test.php
public function test_get_team_page_return_null_if_option_is_not_set() {
}
function get_team_page() {
$option = get_option( TEAM_PAGE_OPTION, 0 );
if ( ! $option ) {
return null; // <- This is what we want to test.
}
$post = get_post( $option );
if ( ! $post ) {
return null;
}
return $post;
}
public function test_get_team_page_return_null_if_option_is_not_set() {
Monkey\Functions::expect( 'get_option' )
->with( TEAM_PAGE_OPTION, \Mockery::any() )
->andReturn( 0 );
static::assertSame( null, get_team_page() );
}
tests/js/exercise4/LoremIpsumEdit.test.js
describe( '<LoremIpsumEdit />', () => {
test( 'should ensure at least one paragraph before mounting', () => {
} );
} );
class LoremIpsumEdit extends Component {
componentWillMount() {
const { paragraphs } = this.props.attributes;
if ( ! paragraphs || ! paragraphs.length ) {
this.updateParagraphs( 1 );
}
}
// ...
}
jest.mock( '../../../../js/src/utils/text', () => ( {
getRandomParagraph: () => 'Random paragraph.',
} ) );
describe( '<LoremIpsumEdit />', () => {
test( 'should ensure at least one paragraph before mounting', () => {
const setAttributes = jest.fn();
shallow(
<LoremIpsumEdit setAttributes={ setAttributes } />
);
expect( setAttributes ).toHaveBeenCalledWith( {
paragraphs: [
'Random paragraph.',
],
} );
} );
} );
static::assertTrue( did_learn_stuff() );