SlideShare a Scribd company logo
Testing the
Untestable
Unit Tests are Great !
• Proof that your code does what it is supposed to do
• Proof that refactoring code doesn’t break its functionality
• Automated Regressions Tests
• Verify that new code doesn’t break existing code
• Unit Tests are a part of the documentation of the code
TDD is Great !
• Writing tests helps design how the code should work
• Write tests as we define the public methods
• All green tests tell us when the coding is complete
• Bugfixing
• Write test for bug
• Fix bug so that test passes
Testing the Untestable
Testing the Untestable
• 100% Code Coverage is over-rated
• Prioritise writing Tests for the code that needs testing
• Diminishing returns
Testing the Untestable
• Test Doubles
• Let the code run
• Mock as little as possible
• Database access
• E-Mailing
• Test values
Testing the Untestable
• Constructor does Real Work
• Indirect Collaborators
• Global State & Singletons
• Class Does Too Much
Testing the Untestable
Constructor does Real Work
• Difficulties
• Creating/Initializing Collaborators
• Communicating with other services, and logic to set up its own
state
• Forcing subclasses/mocks to inherit unwanted behaviour
• Prevents instantiation or altering collaborators in the test.
Testing the Untestable
Constructor does Real Work
• Warning Signs
• new keyword in a constructor
• Static method calls in a constructor
• Anything more than argument validation and field assignment in constructors
• Object not fully initialized after the constructor finishes
• Control flow (conditional or looping logic) in a constructor
• Separate Initialisation methods
Testing the Untestable
Constructor does Real Work
• Why?
• Inflexible and prematurely coupled design
• Violates Single Responsibility Principle
• Testing Directly is Difficult
• Subclassing and Overriding to Test is Still Flawed
• It Forces Collaborators on Us
Testing the Untestable
Indirect Collaborators
• Difficulties
• “Middle Men” make change difficult
• Unnecessary complexity and code bloat
Testing the Untestable
Indirect Collaborators
• Warning Signs
• Objects are passed in but never used directly
• Only used to get access to other objects
• Suspicious names like context, environment, principal,
container, or manager
Testing the Untestable
Indirect Collaborators
• Why?
• Hard to identify the collaborator methods that we need to mock
• Necessary to create mocks that return other mocks
Testing the Untestable
Global State & Singletons
• Difficulties
• Global state mutated in one test can cause a subsequent or
parallel test to fail unexpectedly.
• Forcing subclasses/mocks to inherit unwanted behaviour
• Prevents instantiation or altering collaborators in the test.
Testing the Untestable
Global State & Singletons
• Warning Signs
• Use of Singletons
• Using Static Properties or Methods
• Using Registries or Service Locators
• Static Initialisation Blocks
Testing the Untestable
Global State & Singletons
• Why?
• Changes to global state can be made from anywhere
• Unexpected initial state for individual tests
Testing the Untestable
• Class Does Too Much
• Difficulties
• Tests for individual Methods can’t easily be isolated from other
methods
Testing the Untestable
• Class Does Too Much
• Warning Signs
• Class description uses the word “and”
• Too many methods
• Too many properties
• Too many collaborators
Testing the Untestable
The ideal number of arguments for a function is zero (niladic). Next
comes one (monadic) followed closely by two (dyadic). Three
arguments (triadic) should be avoided where possible. More than three
(polyadic) requires very special justification—and then shouldn't be
used anyway.
Bob Martin – “Clean Code”
Testing the Untestable
public function __construct(
MagentoFrameworkModelContext $context,
MagentoFrameworkViewDesignInterface $design,
MagentoFrameworkRegistry $registry,
MagentoStoreModelAppEmulation $appEmulation,
MagentoStoreModelStoreManagerInterface $storeManager,
MagentoFrameworkAppRequestInterface $request,
MagentoNewsletterModelTemplateFilter $filter,
MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig,
MagentoNewsletterModelTemplateFactory $templateFactory,
MagentoFrameworkFilterFilterManager $filterManager,
array $data = []
) {
parent::__construct($context, $design, $registry, $appEmulation, $storeManager, $data);
$this->_storeManager = $storeManager;
$this->_request = $request;
$this->_filter = $filter;
$this->_scopeConfig = $scopeConfig;
$this->_templateFactory = $templateFactory;
$this->_filterManager = $filterManager;
}
Testing the Untestable
• Class Does Too Much
• Why?
• Difficult to modify behaviour of one responsibility without
changing/breaking others
• Muddled Responsibilities
Testing the Untestable
namespace reporting;
class dateCalculator {
protected $currentDate;
public function __construct() {
$this->currentDate = new DateTime();
}
public function weekToDate() {
$startDate = clone $this->currentDate;
$startDate->modify('last monday');
return new dateRange(
$startDate->modify('midnight'),
(clone $this->currentDate)->modify('midnight')
);
}
}
Testing the Untestable
namespace reporting;
class dateCalculatorTest extends PHPUnitFrameworkTestCase {
public function testDateCalculatorWeek() {
$dateCalculator = new dateCalculator();
$week = $dateCalculator->weekToDate();
$this->assertInstanceOf('reportingdateRange', $week);
$this->assertInstanceOf('DateTime', $week->startDate);
$this->assertEquals(
DateTime::createFromFormat('Y-m-d|', '2018-01-08'),
$week->startDate
);
$this->assertInstanceOf('DateTime', $week->endDate);
$this->assertEquals(
DateTime::createFromFormat('Y-m-d|', '2018-01-10'),
$week->endDate
);
}
}
Testing the Untestable
• Dependency Injection
• Inject either a date value or a DateTime object into the constructor
Testing the Untestable
namespace reporting;
class dateCalculator {
protected $currentDate;
public function __construct(string $date = 'now') {
$this->currentDate = new DateTime($date);
}
public function weekToDate() {
$startDate = clone $this->currentDate;
$startDate->modify('last monday');
return new dateRange(
$startDate->modify('midnight'),
(clone $this->currentDate)->modify('midnight')
);
}
}
Testing the Untestable
namespace reporting;
class dateCalculator {
protected $currentDate;
public function __construct(DateTime $dto) {
$this->currentDate = $dto;
}
public function weekToDate() {
$startDate = clone $this->currentDate;
$startDate->modify('last monday');
return new dateRange(
$startDate->modify('midnight'),
(clone $this->currentDate)->modify('midnight')
);
}
}
Testing the Untestable
• Dependency Injection (Inversion of Control)
• Inject either a date value or a DateTime object into the constructor
• Not always an option if we can’t change the codebase
• e.g. If we’re writing tests preparatory to refactoring
• May require significant changes wherever the class we’re testing is
used within the main codebase
• Or introducing a Dependency Injection Container
Testing the Untestable
• In the case of DateTime, we can modify the the
DateTime constructor
• If we’re using namespacing
Testing the Untestable
class dateCalculator {
protected $currentDate;
public function __construct() {
$this->currentDate = new DateTime("@" . time());
}
....
}
Testing the Untestable
namespace reporting;
function time() {
return strtotime('2018-01-10 01:23:45');
}
class dateCalculatorTest extends PHPUnitFrameworkTestCase {
public function testDateCalculatorWeek() {
$dateCalculator = new dateCalculator();
$week = $dateCalculator->weekToDate();
.... Assertions
}
}
Testing the Untestable
• In the case of DateTime, we can modify the the
DateTime constructor
• If we’re using namespacing
• Overriding the time() function with a namespaced
time() function
• So we set our own value to be returned by a call to time()
Testing the Untestable
• Move the assignment of $currentDate and the
instantiation of the DateTime to a separate method
called from the constructor
• We can then mock that new method in our tests
Testing the Untestable
class dateCalculator {
protected $currentDate;
protected function getCurrentDate() {
return new DateTime();
}
public function __construct() {
$this->currentDate = $this->getCurrentDate();
}
....
}
Testing the Untestable
class dateCalculatorTest extends PHPUnitFrameworkTestCase {
public function testDateCalculatorWeek() {
$dateCalculator = $this->getMockBuilder('reportingdateCalculator')
->setMethods(['getCurrentDate'])
->getMock();
$dateCalculator->expects($this->once())
->method('getCurrentDate')
->will(
$this->returnValue(new DateTime('2018-01-10 01:23:45'))
);
$week = $dateCalculator->weekToDate();
.... Assertions
}
}
Testing the Untestable
• Or Use Reflection
Testing the Untestable
class dateCalculator {
protected $currentDate;
protected function getCurrentDate() {
return new DateTime();
}
public function __construct() {
$this->currentDate = $this->getCurrentDate();
}
....
}
Testing the Untestable
class dateCalculatorTest extends PHPUnitFrameworkTestCase {
public function testDateCalculatorWeek() {
$dateCalculator = $this->getMockBuilder('reportingdateCalculator')
->disableOriginalConstructor()
->setMethods(['getCurrentDate'])
->getMock();
$dateCalculator->expects($this->once())
->method('getCurrentDate')
->will(
$this->returnValue(new DateTime('2018-01-10 01:23:45'))
);
....
}
}
Testing the Untestable
class dateCalculatorTest extends PHPUnitFrameworkTestCase {
public function testDateCalculatorWeek() {
....
// now call the constructor
$reflectedClass = new ReflectionClass('reportingdateCalculator');
$constructor = $reflectedClass->getConstructor();
$constructor->invoke($dateCalculator);
$week = $dateCalculator->weekToDate();
.... Assertions
}
}
Testing the Untestable
• Anonymous Classes
• http://php.net/manual/en/language.oop5.anonymous.php
Testing the Untestable
class dateCalculatorTest extends PHPUnitFrameworkTestCase {
public function testDateCalculatorWeek() {
$dateCalculator = new class() extends dateCalculator {
public function __construct() {
// parent::__construct();
$this->currentDate = new DateTime('2018-01-10 01:23:45');
}
};
$week = $dateCalculator->weekToDate();
.... Assertions
}
}
Testing the Untestable
• https://github.com/MarkBaker/SpyMaster
Testing the Untestable
public function testDateCalculatorWeek() {
$dateCalculator = new dateCalculator();
$spy = (new SpyMasterSpyMaster($dateCalculator))
->infiltrate(SpyMasterSpyMaster::SPY_READ_WRITE);
$spy->currentDate = new DateTime('2018-01-10 01:23:45');
$week = $dateCalculator->weekToDate();
.... Assertions
}
Testing the Untestable
• Make sure we test against different dates using a
phpunit dataprovider
Testing the Untestable
/**
* @dataProvider testDateCalculatorWeekProvider
**/
public function testDateCalculatorWeek($baseline, $expectedResult) {
$dateCalculator = new class($baseline) extends dateCalculator {
public function __construct($baseline) {
// parent::__construct();
$this->currentDate = new DateTime($baseline);
}
};
$week = $dateCalculator->weekToDate();
.... Assertions
}
Testing the Untestable
• Mockery
• https://github.com/mockery/mockery
• AspectMock
• https://github.com/Codeception/AspectMock
Testing the Untestable
• Mocking the Filesystem
• https://github.com/mikey179/vfsStream
Testing the Untestable
• Assertions for HTML Markup
• https://github.com/stevegrunwell/phpunit-markup-assertions
Who am I?
Mark Baker
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171
http://markbakeruk.net

More Related Content

PPTX
Principles and patterns for test driven development
PPTX
Refactoring Legacy Web Forms for Test Automation
PDF
Write readable tests
PPTX
TDD & BDD
PDF
Testing for Pragmatic People
ODP
Writing useful automated tests for the single page applications you build
PDF
TDD, BDD and mocks
PPTX
Clean tests good tests
Principles and patterns for test driven development
Refactoring Legacy Web Forms for Test Automation
Write readable tests
TDD & BDD
Testing for Pragmatic People
Writing useful automated tests for the single page applications you build
TDD, BDD and mocks
Clean tests good tests

What's hot (20)

PPTX
Apex Testing and Best Practices
PPTX
Intro to TDD and BDD
PDF
Why Your Test Suite Sucks - PHPCon PL 2015
PDF
Pitfalls Of Tdd Adoption by Bartosz Bankowski
PPTX
VT.NET 20160411: An Intro to Test Driven Development (TDD)
PDF
Unit Testing Done Right
PPTX
Grails Spock Testing
PDF
Unit test-using-spock in Grails
KEY
How To Test Everything
PDF
Unit testing legacy code
PPT
Google mock for dummies
ODP
Grails unit testing
PDF
Test Driven Development
PDF
TDD CrashCourse Part3: TDD Techniques
PDF
Angular Unit Testing NDC Minn 2018
PDF
Unit Testing Best Practices
PPTX
Automated testing of ASP .Net Core applications
PDF
Working Effectively with Legacy Code: Lessons in Practice
PDF
Write testable code in java, best practices
PPTX
Unit tests and TDD
Apex Testing and Best Practices
Intro to TDD and BDD
Why Your Test Suite Sucks - PHPCon PL 2015
Pitfalls Of Tdd Adoption by Bartosz Bankowski
VT.NET 20160411: An Intro to Test Driven Development (TDD)
Unit Testing Done Right
Grails Spock Testing
Unit test-using-spock in Grails
How To Test Everything
Unit testing legacy code
Google mock for dummies
Grails unit testing
Test Driven Development
TDD CrashCourse Part3: TDD Techniques
Angular Unit Testing NDC Minn 2018
Unit Testing Best Practices
Automated testing of ASP .Net Core applications
Working Effectively with Legacy Code: Lessons in Practice
Write testable code in java, best practices
Unit tests and TDD
Ad

Similar to Testing the Untestable (20)

PDF
Testing and TDD - KoJUG
PPTX
Code Testability
PDF
Getting Started With Testing
PPTX
Unit testing
PPT
types of testing with descriptions and examples
PDF
Unit testing basic
PPTX
The relationship between test and production code quality (@ SIG)
PPTX
Object Oriented Testing Strategy.pptx
PPTX
Testing object oriented software.pptx
PDF
Unit testing with PHPUnit - there's life outside of TDD
PPTX
Unit Testing talk
PDF
Unit testing - An introduction
PPTX
TDD Training
PDF
Testing untestable code - IPC12
PPTX
Java Unit Testing
PPTX
Unit Testing FAQ
PDF
Breaking Dependencies to Allow Unit Testing
PDF
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
PDF
Unit Testing
PPTX
Testing the untestable
Testing and TDD - KoJUG
Code Testability
Getting Started With Testing
Unit testing
types of testing with descriptions and examples
Unit testing basic
The relationship between test and production code quality (@ SIG)
Object Oriented Testing Strategy.pptx
Testing object oriented software.pptx
Unit testing with PHPUnit - there's life outside of TDD
Unit Testing talk
Unit testing - An introduction
TDD Training
Testing untestable code - IPC12
Java Unit Testing
Unit Testing FAQ
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Unit Testing
Testing the untestable
Ad

More from Mark Baker (20)

PPTX
Looping the Loop with SPL Iterators
PPTX
Looping the Loop with SPL Iterators
PPTX
Looping the Loop with SPL Iterators
PPTX
Deploying Straight to Production
PPTX
Deploying Straight to Production
PPTX
Deploying Straight to Production
PPTX
A Brief History of Elephpants
PPTX
Aspects of love slideshare
PPTX
Does the SPL still have any relevance in the Brave New World of PHP7?
PPTX
A Brief History of ElePHPants
PPTX
Coding Horrors
PPTX
Anonymous classes2
PPTX
Anonymous Classes: Behind the Mask
PPTX
Does the SPL still have any relevance in the Brave New World of PHP7?
PPTX
Coding Horrors
PPTX
Does the SPL still have any relevance in the Brave New World of PHP7?
PPTX
Giving birth to an ElePHPant
PPTX
A Functional Guide to Cat Herding with PHP Generators
PPTX
A Functional Guide to Cat Herding with PHP Generators
PPTX
SPL - The Undiscovered Library - PHPBarcelona 2015
Looping the Loop with SPL Iterators
Looping the Loop with SPL Iterators
Looping the Loop with SPL Iterators
Deploying Straight to Production
Deploying Straight to Production
Deploying Straight to Production
A Brief History of Elephpants
Aspects of love slideshare
Does the SPL still have any relevance in the Brave New World of PHP7?
A Brief History of ElePHPants
Coding Horrors
Anonymous classes2
Anonymous Classes: Behind the Mask
Does the SPL still have any relevance in the Brave New World of PHP7?
Coding Horrors
Does the SPL still have any relevance in the Brave New World of PHP7?
Giving birth to an ElePHPant
A Functional Guide to Cat Herding with PHP Generators
A Functional Guide to Cat Herding with PHP Generators
SPL - The Undiscovered Library - PHPBarcelona 2015

Recently uploaded (20)

PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PDF
medical staffing services at VALiNTRY
PDF
System and Network Administraation Chapter 3
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
AI in Product Development-omnex systems
PDF
PTS Company Brochure 2025 (1).pdf.......
PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
top salesforce developer skills in 2025.pdf
Which alternative to Crystal Reports is best for small or large businesses.pdf
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Design an Analysis of Algorithms II-SECS-1021-03
Upgrade and Innovation Strategies for SAP ERP Customers
Design an Analysis of Algorithms I-SECS-1021-03
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
medical staffing services at VALiNTRY
System and Network Administraation Chapter 3
Wondershare Filmora 15 Crack With Activation Key [2025
AI in Product Development-omnex systems
PTS Company Brochure 2025 (1).pdf.......
VVF-Customer-Presentation2025-Ver1.9.pptx
wealthsignaloriginal-com-DS-text-... (1).pdf
Odoo Companies in India – Driving Business Transformation.pdf
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
top salesforce developer skills in 2025.pdf

Testing the Untestable

  • 2. Unit Tests are Great ! • Proof that your code does what it is supposed to do • Proof that refactoring code doesn’t break its functionality • Automated Regressions Tests • Verify that new code doesn’t break existing code • Unit Tests are a part of the documentation of the code
  • 3. TDD is Great ! • Writing tests helps design how the code should work • Write tests as we define the public methods • All green tests tell us when the coding is complete • Bugfixing • Write test for bug • Fix bug so that test passes
  • 5. Testing the Untestable • 100% Code Coverage is over-rated • Prioritise writing Tests for the code that needs testing • Diminishing returns
  • 6. Testing the Untestable • Test Doubles • Let the code run • Mock as little as possible • Database access • E-Mailing • Test values
  • 7. Testing the Untestable • Constructor does Real Work • Indirect Collaborators • Global State & Singletons • Class Does Too Much
  • 8. Testing the Untestable Constructor does Real Work • Difficulties • Creating/Initializing Collaborators • Communicating with other services, and logic to set up its own state • Forcing subclasses/mocks to inherit unwanted behaviour • Prevents instantiation or altering collaborators in the test.
  • 9. Testing the Untestable Constructor does Real Work • Warning Signs • new keyword in a constructor • Static method calls in a constructor • Anything more than argument validation and field assignment in constructors • Object not fully initialized after the constructor finishes • Control flow (conditional or looping logic) in a constructor • Separate Initialisation methods
  • 10. Testing the Untestable Constructor does Real Work • Why? • Inflexible and prematurely coupled design • Violates Single Responsibility Principle • Testing Directly is Difficult • Subclassing and Overriding to Test is Still Flawed • It Forces Collaborators on Us
  • 11. Testing the Untestable Indirect Collaborators • Difficulties • “Middle Men” make change difficult • Unnecessary complexity and code bloat
  • 12. Testing the Untestable Indirect Collaborators • Warning Signs • Objects are passed in but never used directly • Only used to get access to other objects • Suspicious names like context, environment, principal, container, or manager
  • 13. Testing the Untestable Indirect Collaborators • Why? • Hard to identify the collaborator methods that we need to mock • Necessary to create mocks that return other mocks
  • 14. Testing the Untestable Global State & Singletons • Difficulties • Global state mutated in one test can cause a subsequent or parallel test to fail unexpectedly. • Forcing subclasses/mocks to inherit unwanted behaviour • Prevents instantiation or altering collaborators in the test.
  • 15. Testing the Untestable Global State & Singletons • Warning Signs • Use of Singletons • Using Static Properties or Methods • Using Registries or Service Locators • Static Initialisation Blocks
  • 16. Testing the Untestable Global State & Singletons • Why? • Changes to global state can be made from anywhere • Unexpected initial state for individual tests
  • 17. Testing the Untestable • Class Does Too Much • Difficulties • Tests for individual Methods can’t easily be isolated from other methods
  • 18. Testing the Untestable • Class Does Too Much • Warning Signs • Class description uses the word “and” • Too many methods • Too many properties • Too many collaborators
  • 19. Testing the Untestable The ideal number of arguments for a function is zero (niladic). Next comes one (monadic) followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn't be used anyway. Bob Martin – “Clean Code”
  • 20. Testing the Untestable public function __construct( MagentoFrameworkModelContext $context, MagentoFrameworkViewDesignInterface $design, MagentoFrameworkRegistry $registry, MagentoStoreModelAppEmulation $appEmulation, MagentoStoreModelStoreManagerInterface $storeManager, MagentoFrameworkAppRequestInterface $request, MagentoNewsletterModelTemplateFilter $filter, MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig, MagentoNewsletterModelTemplateFactory $templateFactory, MagentoFrameworkFilterFilterManager $filterManager, array $data = [] ) { parent::__construct($context, $design, $registry, $appEmulation, $storeManager, $data); $this->_storeManager = $storeManager; $this->_request = $request; $this->_filter = $filter; $this->_scopeConfig = $scopeConfig; $this->_templateFactory = $templateFactory; $this->_filterManager = $filterManager; }
  • 21. Testing the Untestable • Class Does Too Much • Why? • Difficult to modify behaviour of one responsibility without changing/breaking others • Muddled Responsibilities
  • 22. Testing the Untestable namespace reporting; class dateCalculator { protected $currentDate; public function __construct() { $this->currentDate = new DateTime(); } public function weekToDate() { $startDate = clone $this->currentDate; $startDate->modify('last monday'); return new dateRange( $startDate->modify('midnight'), (clone $this->currentDate)->modify('midnight') ); } }
  • 23. Testing the Untestable namespace reporting; class dateCalculatorTest extends PHPUnitFrameworkTestCase { public function testDateCalculatorWeek() { $dateCalculator = new dateCalculator(); $week = $dateCalculator->weekToDate(); $this->assertInstanceOf('reportingdateRange', $week); $this->assertInstanceOf('DateTime', $week->startDate); $this->assertEquals( DateTime::createFromFormat('Y-m-d|', '2018-01-08'), $week->startDate ); $this->assertInstanceOf('DateTime', $week->endDate); $this->assertEquals( DateTime::createFromFormat('Y-m-d|', '2018-01-10'), $week->endDate ); } }
  • 24. Testing the Untestable • Dependency Injection • Inject either a date value or a DateTime object into the constructor
  • 25. Testing the Untestable namespace reporting; class dateCalculator { protected $currentDate; public function __construct(string $date = 'now') { $this->currentDate = new DateTime($date); } public function weekToDate() { $startDate = clone $this->currentDate; $startDate->modify('last monday'); return new dateRange( $startDate->modify('midnight'), (clone $this->currentDate)->modify('midnight') ); } }
  • 26. Testing the Untestable namespace reporting; class dateCalculator { protected $currentDate; public function __construct(DateTime $dto) { $this->currentDate = $dto; } public function weekToDate() { $startDate = clone $this->currentDate; $startDate->modify('last monday'); return new dateRange( $startDate->modify('midnight'), (clone $this->currentDate)->modify('midnight') ); } }
  • 27. Testing the Untestable • Dependency Injection (Inversion of Control) • Inject either a date value or a DateTime object into the constructor • Not always an option if we can’t change the codebase • e.g. If we’re writing tests preparatory to refactoring • May require significant changes wherever the class we’re testing is used within the main codebase • Or introducing a Dependency Injection Container
  • 28. Testing the Untestable • In the case of DateTime, we can modify the the DateTime constructor • If we’re using namespacing
  • 29. Testing the Untestable class dateCalculator { protected $currentDate; public function __construct() { $this->currentDate = new DateTime("@" . time()); } .... }
  • 30. Testing the Untestable namespace reporting; function time() { return strtotime('2018-01-10 01:23:45'); } class dateCalculatorTest extends PHPUnitFrameworkTestCase { public function testDateCalculatorWeek() { $dateCalculator = new dateCalculator(); $week = $dateCalculator->weekToDate(); .... Assertions } }
  • 31. Testing the Untestable • In the case of DateTime, we can modify the the DateTime constructor • If we’re using namespacing • Overriding the time() function with a namespaced time() function • So we set our own value to be returned by a call to time()
  • 32. Testing the Untestable • Move the assignment of $currentDate and the instantiation of the DateTime to a separate method called from the constructor • We can then mock that new method in our tests
  • 33. Testing the Untestable class dateCalculator { protected $currentDate; protected function getCurrentDate() { return new DateTime(); } public function __construct() { $this->currentDate = $this->getCurrentDate(); } .... }
  • 34. Testing the Untestable class dateCalculatorTest extends PHPUnitFrameworkTestCase { public function testDateCalculatorWeek() { $dateCalculator = $this->getMockBuilder('reportingdateCalculator') ->setMethods(['getCurrentDate']) ->getMock(); $dateCalculator->expects($this->once()) ->method('getCurrentDate') ->will( $this->returnValue(new DateTime('2018-01-10 01:23:45')) ); $week = $dateCalculator->weekToDate(); .... Assertions } }
  • 35. Testing the Untestable • Or Use Reflection
  • 36. Testing the Untestable class dateCalculator { protected $currentDate; protected function getCurrentDate() { return new DateTime(); } public function __construct() { $this->currentDate = $this->getCurrentDate(); } .... }
  • 37. Testing the Untestable class dateCalculatorTest extends PHPUnitFrameworkTestCase { public function testDateCalculatorWeek() { $dateCalculator = $this->getMockBuilder('reportingdateCalculator') ->disableOriginalConstructor() ->setMethods(['getCurrentDate']) ->getMock(); $dateCalculator->expects($this->once()) ->method('getCurrentDate') ->will( $this->returnValue(new DateTime('2018-01-10 01:23:45')) ); .... } }
  • 38. Testing the Untestable class dateCalculatorTest extends PHPUnitFrameworkTestCase { public function testDateCalculatorWeek() { .... // now call the constructor $reflectedClass = new ReflectionClass('reportingdateCalculator'); $constructor = $reflectedClass->getConstructor(); $constructor->invoke($dateCalculator); $week = $dateCalculator->weekToDate(); .... Assertions } }
  • 39. Testing the Untestable • Anonymous Classes • http://php.net/manual/en/language.oop5.anonymous.php
  • 40. Testing the Untestable class dateCalculatorTest extends PHPUnitFrameworkTestCase { public function testDateCalculatorWeek() { $dateCalculator = new class() extends dateCalculator { public function __construct() { // parent::__construct(); $this->currentDate = new DateTime('2018-01-10 01:23:45'); } }; $week = $dateCalculator->weekToDate(); .... Assertions } }
  • 41. Testing the Untestable • https://github.com/MarkBaker/SpyMaster
  • 42. Testing the Untestable public function testDateCalculatorWeek() { $dateCalculator = new dateCalculator(); $spy = (new SpyMasterSpyMaster($dateCalculator)) ->infiltrate(SpyMasterSpyMaster::SPY_READ_WRITE); $spy->currentDate = new DateTime('2018-01-10 01:23:45'); $week = $dateCalculator->weekToDate(); .... Assertions }
  • 43. Testing the Untestable • Make sure we test against different dates using a phpunit dataprovider
  • 44. Testing the Untestable /** * @dataProvider testDateCalculatorWeekProvider **/ public function testDateCalculatorWeek($baseline, $expectedResult) { $dateCalculator = new class($baseline) extends dateCalculator { public function __construct($baseline) { // parent::__construct(); $this->currentDate = new DateTime($baseline); } }; $week = $dateCalculator->weekToDate(); .... Assertions }
  • 45. Testing the Untestable • Mockery • https://github.com/mockery/mockery • AspectMock • https://github.com/Codeception/AspectMock
  • 46. Testing the Untestable • Mocking the Filesystem • https://github.com/mikey179/vfsStream
  • 47. Testing the Untestable • Assertions for HTML Markup • https://github.com/stevegrunwell/phpunit-markup-assertions
  • 48. Who am I? Mark Baker @Mark_Baker https://github.com/MarkBaker http://uk.linkedin.com/pub/mark-baker/b/572/171 http://markbakeruk.net

Editor's Notes

  • #24: Problematic test only works on 10th January 2018
  • #26: Best solution is to modify the code to inject the date into the constructor, either as a date-recognisable string
  • #27: Or as a DateTime object This has the added advantage of allowing reports to be re-run for a particular historic date
  • #41: Using an anonymous class to extend the class being tested and override the constructor