Laravel Testing Decoded



Comments



Description

Laravel Testing Decoded The testing book you’ve been waiting for. JeffreyWay This book is for sale at http://leanpub.com/laravel-testing-decoded This version was published on 2013-08-06 This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do. ©2013 JeffreyWay Tweet This Book! Please help JeffreyWay by spreading the word about this book on Twitter! The suggested tweet for this book is: Just bought @jeffrey_way’s new book, Laravel Testing Decoded! The suggested hashtag for this book is #laravelTesting. Find out what other people are saying about the book by clicking on this link to search for this hashtag on Twitter: https://twitter.com/search/#laravelTesting Contents Welcome . . . . . . . . . . . . It Has Begun . . . . . . . . . Is This Book For Me? . . . . Why Laravel-Specific? . . . Exercises . . . . . . . . . . . Errata . . . . . . . . . . . . How to Consume This Book Get in Touch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 2 3 3 3 3 Into the Great Wide Open . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Chapter 1: Test All The Things . . . . . . . . . . . . . . . . . . . . . You Already Test . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Wins From TDD . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Contribution . . . . . . . . . . . . . . . . . . . . . . . . . . 3. Big-Boy Pants . . . . . . . . . . . . . . . . . . . . . . . . . 4. Testability Improves Architecture . . . . . . . . . . . . . . 5. Documentation . . . . . . . . . . . . . . . . . . . . . . . . 6. It’s Fun . . . . . . . . . . . . . . . . . . . . . . . . . . . . . What Should I Test? . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Signs of Untestable Code . . . . . . . . . . . . . . . . . . . . . . 1. New Operators . . . . . . . . . . . . . . . . . . . . . . . . 2. Control-Freak Constructors . . . . . . . . . . . . . . . . . . 3. And, And, And . . . . . . . . . . . . . . . . . . . . . . . . 4 Ways to Spot a Class With Too Many Responsibilities 4. Too Many Paths? Polymorphism to the Rescue! . . . . . . . 5. Too Many Dependencies . . . . . . . . . . . . . . . . . . . 6. Too Many Bugs . . . . . . . . . . . . . . . . . . . . . . . . Test Jargon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unit Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . Model Testing . . . . . . . . . . . . . . . . . . . . . . . . . . Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . Functional (Controller) Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 6 6 6 7 8 8 8 8 9 9 11 12 12 13 15 15 16 16 17 17 17 CONTENTS Acceptance Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Relax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 19 Chapter 2: Introducing PHPUnit . . . . . . Installation . . . . . . . . . . . . . . . . . Making Packages Available Globally Assertions 101 . . . . . . . . . . . . . . . Decoding A Test Class Structure . . assertTrue . . . . . . . . . . . . . . assertEquals . . . . . . . . . . . . . assertSame . . . . . . . . . . . . . . assertContains . . . . . . . . . . . . assertArrayHasKey . . . . . . . . . assertInternalType . . . . . . . . . . assertInstanceOf . . . . . . . . . . . Asserting Exceptions . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 23 24 25 26 26 27 28 29 30 30 31 32 Chapter 3: Configuring PHPUnit Options . . . . . . . . . . . . . Technicolor . . . . . . . . . Bootstrapping . . . . . . . Output Formats . . . . . . XML Configuration File . . . . Continuous Testing . . . . . . . Watching Files . . . . . . . Triggering Multiple Files . . Some Vim-Specific Advice Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 33 33 34 34 35 37 40 41 42 43 Chapter 4: Making PHPUnit Less Verbose . . . . . . . . . . . . . . . . . . . . . . . . . . . Importing Assertions as Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Applying the Laravel Style to PHPUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 44 45 Chapter 5: Unit Testing 101 . . . . . . . My Struggles . . . . . . . . . . . . . Unit Testing . . . . . . . . . . . . . . Arrange, Act, Assert . . . . . . . . . Testing in Isolation . . . . . . . . . . Tests Should Not Be Order-Dependent Test-Driven Development . . . . . . . Behavior-Driven Development . . . . Testing Functions . . . . . . . . . . . Slime vs. Generalize . . . . . . . 47 47 48 48 49 50 50 50 51 54 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Refactoring the Production Code Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . assertValid and assertNotValid . . . . . . . . . . . . . . . Laravel Test Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Refactoring the Tests . . . . . . . . . . . . . . . . . . . . . Cat Years Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Agenda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .CONTENTS Slime . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 79 80 81 82 83 83 84 87 88 Chapter 7: Testing Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Custom Methods . . . . . . . . . . . . . . Final Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Overrides . . . . . . . . . . . . . . . . . . . selectMonth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Models . . Accessors and Mutators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . Factories . . . . Test Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Coding Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Branching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Factories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Extensibility . . . . . . . . . . . . . . . . . . . 55 55 56 59 66 68 69 72 72 74 74 78 Chapter 6: Contributing to Laravel Using TDD Creating a Proposal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 89 89 90 90 91 92 94 99 101 103 103 104 104 105 105 105 106 . . . . . . . . . . selectYear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hands On . . . . . . . . . . . . . . . . . Your Local Copy . . . . . . . . . . . . . . . . . . . . . . . . Generalize . . . . . . . . . . . . . . Making the Test Pass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Asserting Relationships . . . . . . . Validations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testing Classes . . Simple Query Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Project Complete . . . . . . . . . . . . . . . . . . . . . . . Password Hashing Example . . . . . . . . . . . . . . . . . . . . . . . . . What to Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The IoC Container . . . . . . . . . . Partial Mocks . . . . . . . . . . . . . . . . . . . The Hello World of Controller Testing Overloading is Your Friend . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Laravel’s Helper Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Updating the Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 136 137 137 138 139 139 142 142 146 146 147 150 153 156 159 . Redirections . . . . . . . . . . . . . . . . . . . . . . Required Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mocking Events . . . . . . . . . . Try It Out . . . . . . . . . . . . . . . . . . . Databases in Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mocking Decoded . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mockery . . . . . . . . . . . . . . . . . . . . . . . . . Return Values From Mocked Methods Expectations . . . . . . . . . . . 130 130 131 134 135 Chapter 11: Testing Controllers . . . . . . . . 124 124 125 126 127 128 129 Chapter 10: Just Swap That Thang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Structure . . . . . . . . . . . . . . . . . . . Summary . . . .CONTENTS Chapter 8: Easier Testing With Mockery . . . . . . . . . . . . . . . . . . . Testing . . . Installation . . . . . . . . . . . . . . . Summary . . . . . . . . Test Databases . . . . . . . . . . . . . . . . . . . . . . What Does a Controller Do? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The Dilemma . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . Mocking the Database . . . . . . . . . . . . . . . . . . The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Calling Controller Actions . . . . . . . . . . . Crawling the DOM . . . . . . . . . . . . . . . . . . . . . . . . . Simple Mock Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hamcrest . . . . . . . Specifying the Environment Calling Artisan From Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Repositories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 107 108 109 110 113 115 116 118 119 121 123 Chapter 9: Test Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Steps to Testing Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dependency Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testing the Model Generator . . . . . . . . . . . . . . . . . . . . . . . . Resolving . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Solution 1: Defaults . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testing Artisan Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Solution 2: Resolving . . . . . . . . . . . . . . . . . . . Fetch First or Last . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Create the Package . . . . . . . . . . . . . . . . . . . . . . . Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . App Bindings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Making the Test Pass . . . . . . . . . . . . . . Generator Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Planning . . . . . . . . . . . . . . . Fetch Siblings . . 176 176 176 178 181 182 183 183 184 185 187 189 189 190 191 191 195 201 Chapter 14: Testing APIs . . . . . . . . . . . Dependency Injection? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fetch By Position . . . . Constructor Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 164 164 165 166 166 167 169 171 172 173 175 Chapter 13: Test-Driving Artisan Commands Exercise Commands 101 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Scaffolding . . . . . . .CONTENTS Ensure View Contains Text Basic Traversing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 160 160 160 161 161 161 162 163 Chapter 12: The IoC Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Setter Injection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Forms . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Single Responsibility Principle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dependency Injection . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . Capture Text Content . . . . . . . . . . . . . . . . . . Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Options . . . . . . . . . . . . . . Generating the Command . . . . . Arguments . . . . . . Expectations . . . . . . . . . . . . . . . Fetch Children . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Service Providers . . . . . . . . . . . . . . . . . . . . . . . . . . . . Example 2: Automatic Resolution Extra Credit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Generator Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Translating the Feature for Codeception . Check For Error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Configuring Acceptance Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229 229 229 231 232 234 Chapter 15: Acceptance Testing With Codeception An Amuse Bouche . . . . . . . . . . . . . . . . . . . Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Specifying Options . . . . . . . . . . . . . . Bootstrapping . . . . . . . . . . . . . . . Authentication . . . . . . . Updating a Photo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .CONTENTS APIs in Laravel . . . . . . . . . . . . . . . . . . . . Local Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Register Routes . . . . . . . . . . . . . . . . . . . . . . Decoding the Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . Building the Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Set the Authenticated User . . . Use a Database in Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 217 218 219 220 220 221 222 223 223 224 224 225 225 226 228 Chapter 16: Authentication With Codeception Exercise The Feature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testing APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Migrate the Database for Each Test . . . . . . . . Fetch All Photos For the Authenticated User . . . . . . . . . . . . . . . . . . . . . . . . Refactoring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Resources . . . . Return JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202 202 202 204 205 207 207 208 208 209 209 209 210 210 211 212 213 214 215 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Factories . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Testing Refresher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Enable Filters . . . . . 2. . . . . . . . . . . Three Components to Writing an API in Laravel 1. . . . . . . . . . . . . . . . . Writing the First Test . Generate a Test . . . . . . . . . . Acceptance Testing . . . . . Manual Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . User Must Be Authenticated . . . . . . . . . . . . . . . . . . . Global Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Test Examples . . . . . . . . . . . . . Running All Tests . . . . . . . . Route Prefixing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Hello. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. . 262 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Build Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Register Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Absolute Basics . Configure . . . . . . . . . . 262 Wait a Second… . . . . . . . Summary . . . . . . . . . . . Registering a User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. . . 234 235 236 239 Chapter 17: Functional Testing in Codeception The Laravel4 Module . . . . . . . . . . . . 3. . . The DB Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 240 240 241 241 245 Chapter 18: Continuous Integration With Travis CI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Turn Off Notifications . . . . . . . . . . . . . . . . . 6. . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IRC . 1. . . . . . . . . . . . . . . . . . . . Mock It . . . . . 1. . How Do I Test Protected Methods? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notifications . . . . . . . . Passive Partial Mocks . . . . . . . . . . . . . . . . . . . . I Prefer Using Underscores For Test Names. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Travis . . . . . . . 7. . The Namespacing Trick . . . . Bootstrapping . . . . . . . . . . . . Is That Okay? . . . . . . . . . . . . . . . . . . . . . . . .CONTENTS Authenticating the User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding a Test Database Invalid Credentials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . How Do I Test Global Functions? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Should I Test Getters and Setters? . . . . . . . . . . . . . . . . Set Recipients . . . . . 2. . . . . . . Traditional Partial Mocks . . . . . Why Is PHPUnit Ignoring My Filters? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Can I Mock a Method in the Class I’m Testing? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 247 247 248 249 252 252 253 253 254 254 254 254 254 Frequently Asked Questions (A Living Document) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. . . . . . . . . . . . . . . . . . . . . . . . . . . Register Hooks . . . . . . . . . . . . . . . . . . . . . Summary . How Do I Create a SQL Dump for Codeception Tests? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 255 255 256 257 258 258 258 259 260 260 261 Goodbye . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Connect . . . . . . . . . . . . . . . . . . . . Updating TestGuy . . . . . . . . . . . . . . . . . . . . . when you begin to realize the down-right necessity for testing.5. we require real-life experience. like a father staring at his grown-up daughter. following this pattern will also make you a better developer. what they see is that old sloppy PHP 4 code. So the question is. while. And when the topic of conversation switches to PHP specifically. so does your sloppy. As your application grows. as your ability to manually test the application becomes unrealistic. Before long. or even impossible! It’s at these specific times. with a smile on your face. While the principles of testing (and TDD) are language-agnostic. poorly made WordPress themes from 2008. you might have read a TDD book in the past. or OOP. when it comes to execution. in terms of innovation and software design? Certainly not.to the point of 80% market share . cowboy ways. prepare yourself for the vitriol.get it. untested codebase. there are those who. Is its API inconsistent from time to time? Definitely. That messy. Has its community lead the development world. Sure. In fact. It Has Begun When it comes to programming languages. or Composer. it’s quite possible that your codebase. Welcome to modern software development. well. before we suddenly . everyone has an opinion. The only problem is that testing can be a tricky thing.Welcome I’ve seen it way too many times.when . No. or modern frameworks like Laravel. The naysayers don’t see version 5. or a growing emphasis on test-first development. worse. but.in that wonderful “aha moment” . you’ll. why? Why does PHP dominate . and. is untestable! What you may not realize is that. yes. untestable spaghetti code that you might have snuck into your project in the past will never happen again. like many things in life. Trust me: as soon as you bring the question “how could I test this” to the forefront of every new piece of code. you begin to drown. testing does help to ensure that your code works as expected. and laugh at your crazy. look back to your former self. Despite the fact that the language has matured significantly in the last five years. as it currently stands. Is PHP as beautiful a language as Ruby? No. still can’t help but see PHP as a baby. This book is as much an introduction to TDD. as it is a deep analysis of the Laravel way of testing applications. there are a variety of tools and techniques at your finger tips. we embrace version control and continuous integration. maybe there’s something else to its success. But. we evangelize modern frameworks. the fact that you can create a file. The time for hating PHP is over. It’s not overly sexy. The PHP renaissance has begun. echo hello world. Since when did ease-of-use become something that others mocked? Or. the rest of us will be building things with the latest that the language and surrounding ecosystem have to offer. we share packages through Composer. you know what? We get stuff done. as a Laravel user. Is This Book For Me? The difficult thing about writing a technical book is determining where to draw the line. Can you learn test-driven development from a Java book? Absolutely! Would it be easier through the lens of the language and framework that you already know? Certainly.3 • Laravel 3 (preferably version 4) • Composer Why Laravel-Specific? Sure. in my experiences. we believe in testing (you soon will too). and immediately see the output in a browser is far more powerful and user friendly than we give it credit for. .Welcome 2 competing languages are admittedly more elegant? Well. As long as you have a basic understanding of the following technologies. somebody said “Welcome to the family. We use modern object-oriented techniques. Let’s figure this out together. We’re all in this together. please do continue! • PHP 5. and we do it all with a smile. within minutes. in terms of which prerequisites are required before reading chapter one. however. If you purchased this book. It’s not in beta. it sounds like you could use a bit of help in the testing department. This is the PHP community I love. we welcome newcomers (rather than lock the door). Maybe. rather than a vice. you’re at the forefront of this new modern movement! When I first joined Laravel’s IRC channel. perhaps the simple truth is that PHP is not the new hotness.” Nothing describes our community more beautifully than that. it’s best to take your first steps into this new world in as comfortable shoes as possible. many of the techniques outlined in this book can be applied to any language or framework. Maybe its flexibility is a virtue. The best part is that. welcome to the family. In Laravel spirit. While you’re doing that. Say as much as you want about PHP 4. For instance. be sure to say hello. feel free to flip around to the chapters that interest you most. How to Consume This Book While you can certainly read this book from cover to cover. as you read through this book. Get in Touch It sounds like we’re going to be spending a lot of time together. then you clearly don’t need to read the obligatory “Unit Testing 101” chapter! Skip it. if you already understand the basics of unit testing. and move on to the more interesting bits and pieces. perhaps. and. as you read the chapter. as you work your way through this book. please file an issue on GitHub¹. the downside to a generic testing book is that I wouldn’t be able to demonstrate many of the PHP-specific features and packages that I use in my every-day coding. pull out the computer and join me through each step! Errata Please note that. This includes everything from PHPUnit helper packages. while I’ve made every attempt to ensure that this book is free of errors and typos. Each chapter is self-containing. you’ll also pick up a variety of Laravel-specific tips and tricks. Exercise chapters will be provided. it’s my hope that. Think of these as highly in depth tutorials that you are encouraged to work through. Exercises Sporadically throughout this book. Theory can only take us so far. Finally. If you’d like to attach a face to the author. like Codeception. a five minute hug will be granted to each bug filer. So. in addition to improving your testing knowledge. to acceptance testing frameworks. when you come to an Exercise chapter.Welcome 3 Secondly. it’s the actual coding that ultimately commits these patterns and techniques to memory.com/jeffrey_way . my human-ness virtually guarantees that some will sneak in! If you notice any mistakes. • IRC (#laravel channel): JeffreyWay • Twitter: @jeffrey_way² ¹https://github. As a reward.com/JeffreyWay/Laravel-Testing-Decoded ²http://twitter. and I’ll update the book as soon as possible. ask some questions along the way. My wife and animals had long since abandoned me.” Like most things in life. though.com/watch?v=LkCNJRfSZBU .” I did it. The first time that I fully understood what purpose a <div> serves was one such moment. it may sound obvious to you now. if you don’t offer tests for this pull request. true change requires us to plant our feet in the sand. but think back to the early days. when you need to move things around. think to myself. What the heck does wrapping this HTML in a <div> do? The output in the browser looks exactly the same! One day. I was told to think of them as buckets. and yell. ³http://www. then. coding to an interface. Like the snap of a finger. I’m sorry to say. as well. you wouldn’t be manually testing this over and over. Regardless of whether I knew it or not. but I certainly couldn’t blame him. makes sense. including my slow appreciation for objectoriented programming. “You should be testing. Thousands of developers have. As I continued to direct every inch of my mind toward my laptop screen. “Hey. and test-driven development. you only need to reposition the <div>. We refer to these as “aha” moments. my love for testing was not an immediate thing. Wait. As Leeroy Jenkins³ might say. suddenly. at least slightly. no. The night was silent. that’s the wrong book. I understood.” “If you had written tests for this. the seeds had been planted. I could feel it.” and then continue on my existing path. in favor of sleep. when. place your HTML within these buckets. Yes. save for the strangely comforting repetition of my ceiling fan blades. it’s your turn. Now. Similar to most developers.youtube.” “They’re going to laugh at you. as he usually does. and. who was I to complain that they weren’t awake at three in the morning? No. Jeffrey. things were beginning to…click. that’s interesting. Developers know this feeling well: those all too rare moments. Sure. As time moved on. all right: let’s do this! Here’s the testing book you’ve been waiting for. held out the longest. I could recite dozens of unique moments like this one. I’d read an article or book on the subject. the setting was just right.Into the Great Wide Open The night was sultry. “No more! I’m done with the old way. now. what was once impossible to understand. The dog. that gradual nudging in the back of my mind incrementally grew stronger and louder. in this book. and instead write a test. then the door opens. Do I test controllers? What about models? Do views really need tests? What about my framework’s code. unfortunately. then you were testing. but mostly on Scrabble games with my wife. Why do a job that a computer can handle for you (and much quicker. or hitting the database. where one of your test suites is triggered repeatedly as you develop your applications. it can be beneficial to learn why others advocate it as religiously as they do. to automating the entire process. Having said that. where I get to embarrass her in public⁴ if I win). How could that possibly be confusing? Well.com/team/jeffrey-wins-a-bet/ .Chapter 1: Test All The Things Every testing book in existence offers the obligatory “Why Test” chapter. a fairly steep learning curve. Understanding what and how to test is another story. If you’ve ever written console. Find yourself continuously reaching for Google Chrome to test a particular piece of functionality? Close it. This allows for a continuous testing cycle. Where do I start? I’m not a betting man (well. If you think about it. the simple fact that you purchased this book hints that you’re already sold on the concept. things quickly become tricky when you begin researching what to test. we were expert testers.envato. You Already Test The truth is that you’re already a master of testing. Success!” The only problem is that those tests were performed manually. is to transition from manually testing every piece of functionality. but there’s little doubt in my mind that. you’ve found yourself asking these very questions. That may be surprising. it certainly was for me! The basic principle is laughably simple: write tests to prove that your code works as expected. this book will help. ⁴http://notes.log or submitted a form in your web app to test a particular piece of functionality. Hopefully. Testing is easy. or fetching information from web services? And what about the dozen different kinds of testing that I hear folks on StackOverflow referring to? What about test frameworks? PHPUnit? Rspec? Capybara? Codeception? Mink? The list goes on and on. You’ll be amazed by the level of security that this can provide. Even as babies. Learning how to properly test your applications requires. I partially am. at some point in your career (if not right now). “If I twist this knob. too)? Our goal. Eventually (one of the perks).not without manually testing every possible path through the code. other members of the community will begin contributing to your projects when they encounter bugs or hope to implement new functionality. and immediately receiving feedback on whether you screwed up.org/ .” . because you might break the code? If tests were backing up that code. ⁵https://travis-ci. how could you (or they) possibly determine if their changes have broken the code? The answer? You can’t . clicking save. Who has the time to do that for every pull request? Think of a highly tested project as a well-oiled machine.Chapter 1: Test All The Things 6 “When developers first discover the wonders of test-driven development. it’s like gaining entrance to a new and better world with less stress and insecurity. In fact. Imagine making an edit. Security Should you accidentally make a mistake or break a piece of existing functionality. you might think that the only purpose for it is to ensure that your code works as expected. 2. 3. 1. 5. when developers submit pull requests. there are multiple advantages to following a test-driven development cycle. you’d be wrong. If I want to contribute to your project. the test robots will notify you right away. However. But. you’ll likely leverage the convenience and power of social coding through GitHub. Clone the repository Write a test that describes the bug (testThrowsExceptionIfUserNameDoesNotExist) Make the necessary changes to fix it Run the tests to ensure that everything returns green (success) Commit the changes. if your project doesn’t contain a test suite. Initially. If those tests fail. your fear would have been unwarranted.DHH 6 Wins From TDD Testing is a deceptive thing. How much better would you sleep at night? Remember that terribly coded class you were too afraid to refactor. Contribution As you begin developing open source software. I only need to follow a handful of steps: 1. and submit my pull request There are even continuous integration services. 2. 4. I immediately know that it shouldn’t be merged without further tweaking. which will automatically trigger a project’s tests when a pull request is submitted. like Travis⁵. Much of the vitriol directed toward the PHP community is the result of PHP 4 and WordPress code. like Laravel. . 2. just start coding. Let’s not be cowboys. don’t think. when it comes to the PHP community. What could be easier than that? Well. are largely foreign to them. it brought blogging to the masses. we all learn to put on our big-boy pants. and no. We’re better than that. On one hand. We’re developers. though: it certainly hasn’t pushed the boundaries of software craftsmanship. and then style. don’t test. can prove incredibly difficult. Don’t plan. However. the learning curve can be quite steep. it also inadvertently nurtured a community of PHP developers who hesitated to reach beyond WordPress for new projects. insert a loop to fetch the recent posts. One thing’s for sure. In fact. that’s true. We refer to that old-fashioned practice of coding without thinking as being a cowboy. Is WordPress responsible for these side-effects? Yes. This unfortunate truth has had two side-effects: 1.org 3. Making the leap from WordPress to a full-stack framework. Eventually. while frantically refreshing the browser to determine if each change has broken the application. MVC.7 Chapter 1: Test All The Things travis-ci. Big-Boy Pants If I may go on a tangent for a moment. modern practices and patterns. guns a blazing. It also provided an easy-to-use theming framework for developers. This is undeniable. Consequently. such as test-driven development. testing is ignored for 95% (made up number) of the available WordPress plugins. my view is that WordPress has been a double-edged sword.php file. though. Due to the number of new tools and patterns. Create an index. and version control. and must be respected. the answer is yes. leading to a 404 page? Yes? Then write a test to ensure that you catch any breaks as quickly as possible. jumbled code of your past. it’s okay if you take one step at a time. and. What about a utility that fetches your latest tweets and displays them in the sidebar of your website? If the only alternative is to open Google Chrome and check the sidebar. This forces us to remove all constraints and instead focus on readability. Want to know what functionality a particular class offers? Poke around the tests. then you’ll have a full understanding in no time! 6.Chapter 1: Test All The Things 8 An interesting transition takes place. You’ll see for yourself soon enough. Once you become comfortable with the process. then. or system under test). Tests encourage structure by making you design before coding. How can I take this code from red to green? Follow each step until you get there. It’s a beautiful thing! 4. keeping you safe from repeating the wreckless. . making a method perform too many actions. then move on. if they were named properly (meaning that they describe the behavior of the SUT.one that I will repeat multiple times throughout this book . 5. Documentation A huge. out of laziness. The downside. This simple question will be your security blanket. we interact with the class or API before it has been written. Testability Improves Architecture One thing that you’ll learn throughout this book is to bring the question. It may sound silly at first. Who would have thought? What you’ll soon learn is that there’s more to testing than simply verifying that a method performs as expected. Is it possible that one of your routes could break. as a report? Should you test that? Sure. When we test. but I promise you: it’s fun. huge bonus to writing tests is that they provide free documentation for the system. And what geek doesn’t enjoy a good game? A fun side-effect to test-driven development is that it turns your job into a game.is to test anything that has the potential to break. most certainly. There’s a reason why we begin by counting on our fingers. Doing so isn’t testable. How might I test this? to the forefront of every new piece of code. No longer will you get away with. What would be the most readable way to fetch data from this web service? Write it as such. With such a steep learning curve. at least initially. It’s Fun Let’s face it: we’re geeks. watch it fail. when you force yourself to think before coding: it actually improves the quality of the code. What Should I Test? The basic rule . is that this test all the things mantra can quickly become overwhelming. Baby steps is the way to go. What about your custom class that fetches some data from a table and writes it to a file. Learn how to test your models. and then make it work. wikipedia. you break this rule. Or. when it comes to testing. say. you’ll begin to instantly recognize coding pitfalls.Chapter 1: Test All The Things 9 One Note of Caution: This tip is only partially accurate. As your testing chops improve. making note of each anti-pattern. but there is such a thing as over-testing (though this is debated heavily). In effect. Before long. Remember: testing in isolation requires that the class. Testing anything and everything that has the potential to break is a noble goal. you begin to recognize certain patterns. Like instinct. Here are five easy things to look out for: 1. so don’t double up. but. Those should have their own tests. you’ll find yourself silently scanning a piece of code. itself. Your job. is to decide for yourself where this line is drawn for your own applications. you find yourself speaking fluently. 6 Signs of Untestable Code Learning how to test code is a bit like moving to a country where no one speaks your language. New Operators The principles of unit testing dictate that we should test in isolation. A growing sector in the community would argue that it’s best to limit your tests to the areas of your code in which they’re most beneficial. Once you begin littering the new operator throughout your classes. in short. Anti-pattern: . does not instantiate other objects. it’s not the other way around. Tests are meant to serve you.org/wiki/Diminishing_returns . All it takes is pressure…and time (Use Morgan’s Freeman voice when reading that last line). don’t test that your Filesystem class fetches some data from a web service. We’ll cover this concept more in future chapters. anyone can learn this stuff. Eventually. and nothing else. your goal should be to test the current class. in other words. It’s not rocket science that we’re working with here. then don’t bother writing those tests. where is the point of diminishing returns? http://en. the more you push through. if you rarely make mistakes when writing. reader. accessors and mutators. Don’t access the database. at least. PHP. $data = $someClass->fetch('http://example. we must make use of dependency injection religiously. } With this modification. 2 3 4 5 6 public function __construct(Filesystem $file) { $this->file = $file. 6 7 8 $this->assertEquals('foo'. $data). As such. not without recompiling PHP with special extensions. You’ll learn the inner workings soon! For now. Better: 1 protected $file. 5 return $this->data = $file->get($url). While languages like Ruby offer the ability to re-open a class (known as monkey-patching) and override methods (particularly helpful for testing). $file->shouldReceive('get')->once()->andReturn('foo'). does not . 9 10 } . } 7 8 9 10 11 public function fetch($url) { return $this->data = $this->file->get($url). Don’t worry if the code below is foreign to you. allowing for complete testability. 1 2 3 4 public function testFetchesData() { $file = Mockery::mock('Filesystem'). a mocked version of the Filesystem class can be injected.com'). simply try to soak it in. 5 $someClass = new SomeClass($file). unfortunately. 6 7 } This is one of those situations where PHP isn’t quite as flexible as we might hope.Chapter 1: Test All The Things 1 2 3 4 10 public function fetch($url) { // We can't test this! $file = new Filesystem. $this->cache = $cache. Can I have the Filesystem class. Arrange 2. consider refactoring. Cache $cache) { $this->file = $file. Act 3. please? If you’re doing anything beyond that. $this->cache = $cache. $this->write($data).com'). Think of this as your class asking for things. 6 7 8 } Better: 1 2 3 4 5 public function __construct(Filesystem $file. 5 $data = $this->file->get('http://example. Control-Freak Constructors A constructor’s only responsibility should be to assign dependencies.Chapter 1: Test All The Things 11 The only time when it’s acceptable to instantiate a class inside of another class is when that object is what we refer to as a value-object. Tip: Hunt down the new keyword in your classes like a hawk. you’ll repeatedly follow the same process: 1. } The reason why we do this is because. when testing. They’re code smells in PHP (at least for 90% of the cases)! 2. Assert . Cache $cache) { $this->file = $file. Anti-pattern: 1 2 3 4 public function __construct(Filesystem $file. or a simple container with getters and setters that doesn’t do any real work. This makes them considerably easier to test. this class handles the hashing of passwords). • A Validator class is responsible for validating data against a set of rules. each test you write must account for these actions. • A SQLBuilder builds a SQL query. this. a method should be limited to just a few (one is preferable. we hear and understand The Single Responsibility Principle. and. 3. too often. And. every method is dozens of lines long. If you’re having trouble choosing a name for a class. on the other hand. Train yourself to immediately analyze the number of lines in each method. just might be a sign that you’ve gone off track. 3. 4. Tip: Keep it simple: limit your constructors to dependency assignments. And Calculating what responsibility a class should have can be a difficult thing at first. Notice how. in none of the examples above did the word. • A UserAuthenticator class determines if the provided login credentials are correct. . then make some changes. given a set of data. and need to restructure. 2. but putting that knowledge into practice can be tough at first. chances are. Here are some examples to get you started: • A FileLogger class is responsible for logging data to a file. refactoring is in order. then. 4 Ways to Spot a Class With Too Many Responsibilities 1.Chapter 1: Test All The Things 12 If a class’ constructor is littered with its own actions and method calls. when given a username. too. If you find yourself using the word. Sure. occur. And. show the class to one of your developer friends. If. as you’re not forced to juggle multiple objects. even). If they don’t immediately realize the general purpose of the class (oh. this is a clear indication that too much is going on. • A TwitterStream class fetches and returns tweets from the Twitter API. If all else fails. Ideally. and. The simplest way to determine if your class is doing too much is to speak aloud what the class does. like many things in life.com/2012/04/the-aha-moment/ . 8 9 10 11 // other types of accounts here 12 } 13 14 return $balance + ($balance * $rate). you’ll never forget it. or some other type entirely. but unique logic to sub-classes. 15 16 } In situations such as this. This is referred to as The Single Responsibility Principle. Too Many Paths? Polymorphism to the Rescue! Truly understanding what polymorphism is requires an aha moment⁶. break. Here’s an incredibly simplified example: 1 2 3 4 5 6 function addYearlyInterest($balance) { switch ($this->accountType) { case 'checking': $rate = $this->getCheckingInterestRate(). savings. break. The easiest possible way to determine if a class could benefit from polymorphism is to hunt down switch statements (or too many repeated conditionals). But. but can have unique functionality. ⁶https://tutsplus.Chapter 1: Test All The Things 13 Tip: Reduce each class to being responsible for one thing. based on whether the type of account is checking. 4. Imagine a bank account class that should calculate yearly interest differently. once you understand it. a smarter course of action is to extract this similar. 7 case 'savings': $rate = $this->getSavingsInterestRate(). Definition: Polymorphism refers to the act of breaking a complex class into sub-classes which share a common interface. don’t sweat over the syntax. must implement the given methods. This is a nice way to think of them: any implementation. according to the terms of the contract. 1 2 3 4 5 6 class SavingsInterest implements BankInterestInterface { public function getRate() { return .01. We’ll cover it soon enough. This provides us with some protection. 5 6 } 7 8 9 10 $bank = new BankAccount.03. } } Now. the original method can be cleaned up considerably. Again. testing this code is a cinch. now that we no longer need to account for multiple paths through the function. the class is forced to offer a getRate method. } } Create a savings implementation of the interface. . } Create a checking implementation of the interface. // 101 $bank->addYearlyInterest(100.Chapter 1: Test All The Things 14 Define an interface to ensure that you have access to a getRate method. 1 2 3 interface BankInterestInterface { public function getRate(). // 103 Even better. 4 return $balance + ($balance * $rate). You’ll often hear interfaces referred to as contracts. new SavingsInterest). Notice how we’ve type-hinted the $interest variable below. 1 2 3 function addYearlyInterest($balance. 1 2 3 4 5 6 class CheckingInterest implements BankInterestInterface { public function getRate() { return . $bank->addYearlyInterest(100. BankInterestInterface $interest) { $rate = $interest->getRate(). By implementing the interface. new CheckingInterest). This is precisely why we’ve coded to an interface. if it doesn’t exist. as we don’t want to fall into a trap of calling a getRate method on a class. Chapter 1: Test All The Things 1 2 3 4 15 public function testAddYearlyInterest() { $interest = Mockery::mock('BankInterestInterface'); $interest->shouldReceive('getRate')->once()->andReturn(.03); 5 $bank = new BankAccount; $newBalance = $bank->addYearlyInterest(100, $interest); 6 7 8 $this->assertEquals(103, $newBalance); 9 10 } Tip: Polymorphism allows you to split complex classes into small chunks, often referred to as sub-classes. Remember: the smaller the class, the easier it is to test. 5. Too Many Dependencies In the Control Freak Constructor tip, I noted that dependencies should be injected through the class’ constructor. Having said that, if you find that a particular class requires four or more dependencies, this is, more often than not, a tell-tale sign that your class is asking for too much. “I usually re-evaluate any classes with more than four [dependencies].” - Taylor Otwell⁷ A basic principle of object-oriented programming is that there’s a correlation between the number of parameters a class or method accepts, and the degree to which it is flexible (and, in effect testable). Each time that you remove a dependency or parameter, you’re improving the code. If one of your classes lists too many dependencies, consider refactoring. 6. Too Many Bugs I once heard Ben Orenstein remark that bugs love company. Boy, is this a true statement if I’ve ever heard one. If you notice that they crop up in a particular class too frequently, then the code just might be screaming for refactoring and sub-classes. Think about it: the reason for the bug’s existence is because you couldn’t understand the code well enough the first time around; it was too complicated! And guess what? Complicated code often signals untestable code. The coupling may simply be too strong, opening the way for sneaky bugs…and all their pals, too. ⁷https://twitter.com/taylorotwell/status/334789979920285697 Chapter 1: Test All The Things 16 Definition: Coupling refers to the degree in which two components in your system are dependent upon one another. If removing one affects the other, then you’ve unfortunately written tightly coupled code that isn’t easy to change. As Ben beautifully put it, if there was a bug on line seven, then, chances are, there’s also a bug on line eleven. Nip that in the bud as early as possible. Tip: When presented with such bugs, begin asking yourself how you can split the logic up into smaller (easier to test) classes. In addition to improved testability, one perk to this pattern is that it allows for significantly more readable production code. Bugs love company. Pull out the bug spray. Test Jargon I’m not so self-consumed to think that this book will serve as your sole source of testing education (I certainly hope it isn’t). If you’re anything like myself, you’ll find yourself scouring the web late at night for every last fragment of education to fill in those missing pieces in your understanding. In the process, you’ll come across incredibly confusing jargon. Worse, this terminology is inconsistent from language to language! Yikes! In this book - and in the spirit of simplicity - we’ll break things down into their simplest terms. Scan the following definitions, but don’t feel that you must commit them to memory all at once. In truth, in many ways, I’m very much against all this confusing jargon. If a term doesn’t immediately make sense, then it should be changed. The development community should take its cues from astrophysics. “The most accessible field in science, from the point of view of language, is astrophysics. What do you call spots on the sun? Sunspots. Regions of space you fall into and you don’t come out of? Black holes. Big red stars? Red giants. So I take my fellow scientists to task. He’ll use his word, and if I understand it, I’ll say, “Oh, does that mean da-dada-de-da?” - Neil Degrasse Tyson Unit Testing Think of unit testing as going over your classes and methods with a fine-tooth comb, ensuring that each piece of code works exactly like you expect. Unit tests should be executed in isolation, to make the process of debugging as easy as possible. 80% of your tests will be in this style. If it helps, when you think of unit testing, think one object, and one object only. If a test fails, you know exactly where to look. Chapter 1: Test All The Things 17 Model Testing Some members of the Ruby on Rails community associate model testing (even when these tests touch the database) with unit testing. This unfortunately can be a bit misleading. Unit tests should be isolated from all external dependencies. Once you ignore this basic rule, you’re no longer unit testing. You’re writing integration tests (more on that shortly). In this book, when we test our models, we’ll stay true to the traditional definition of unit testing, unless specified otherwise. Imagine that a method in your model is responsible for sending an email. If following good design patterns, you’ll likely have a class that is dedicated to sending email (single responsibility principle). This presents a problem, however: how do we successfully unit test this method, if it calls an external Mailer class? The answer is to use mocks, which we’ll cover extensively in this book. A mock allows us to fake the Mailer class, and write an expectation to ensure that the proper method is called. This way, even if the Mailer component is currently broken (it will have its own tests), we can still verify whether or not the model method is working as expected. Integration Testing If a unit test verifies that code works correctly in isolation, then an integration test will fall on the other end of the spectrum. These tests will flex multiple parts of your application, and typically won’t rely on mocks or stubs. As such, be sure to create a special test database. As an example, think of a car. Sure, the engine and fuel injection system might individually work as expected (each passes its own set of unit tests), but will they work when grouped together? Integration testing verifies this. Functional (Controller) Testing What do you call the process of testing a controller? Some frameworks may refer to this as functional testing (and it is), but we’ll simply stick with - wait for it - controller testing! More traditionally, think of functional testing as a way for you and your team to ensure that the code does what you expect. While unit tests verify each unit of a class, functional testing is a bit broader and can trigger multiple pieces of your application. Most frequently, these tests will be triggered from the outside in, which is why they’re also commonly referred to as System Tests. One important note is that functional tests typically won’t require a server to be running. Acceptance Testing You’ve already learned that functional testing ensures that the code meets the requirements of the development team. However, there will be cases when, even though the tests return green, the final implemented feature will not meet the requirements of the client. This is what we refer to as Chapter 1: Test All The Things 18 acceptance testing. In other words, does this code meet the requirements of the client? Your software can pass all unit, functional, and integration tests, but still fail the acceptance tests, if the client or customer realizes that the feature doesn’t work as they expected. Tip: If functional tests meet a developer’s assumptions and requirements, acceptance tests are intended to verify the client’s expectations. Think of Tuts+ Premium⁸, the subscription-based technical education service that I work for. Recently, we added a new bookmarking feature that allows you to save courses and eBooks that you want to read later. Before the developers can begin writing a single line of code, they first need to understand what the content team’s expectations are - they’re the ones requesting the feature. This requires an acceptance test. 1 2 3 In order to keep track of what to learn next As a member I want to bookmark content Once this acceptance test passes, it may be assumed that the feature has fully been implemented, and meets the client’s (the content team, in this case) expectations. ⁸http://tutsplus.com Chapter 1: Test All The Things 19 The bookmarking feature on Tuts+ Premium required acceptance tests. In the final section of this book, you’ll learn how to write acceptance tests using the Codeception framework. You’ll find that this allows us to use human speak to define how we want to interact with our applications. Are Tests Too Expensive? A common testing myth is that, though they might be beneficial, when it comes to the real world, a client’s budget factors into the equation. As they put it, no client is willing to double your budget for the sole purpose of providing you with more security. Is there merit to this argument? No, no there’s not. In fact, plenty of studies have found that a testdriven development cycle reduces the length of time it takes to complete a project. . Relax I’ll be the first one to tell you that these definitions took me a very long time to learn and appreciate. I certainly don’t expect this to stick after the first reading. Sheesh; we haven’t even gotten to the “It has become infeasible for a software developer to consider himself professional if he does not practice test-driven development. You’ll develop your own style in time. 80% test-after). freely admit that they write the tests after the production code . view this book less as a Bible. “Don’t force yourself to test-first every controller. As such. While it’s fair to say that most developers these days agree that writing tests is vital. medium tests. other equally influential developers. and more.roughly 80% of the time. You’ll also comes across terms. Having said that.youtube.com/watch?feature=player_detailpage&v=KtHQGs3zFAM#t=77s ¹⁰http://37signals.Bob Martin⁹ But. and view (my ratio is typically 20% test-first. Some evangelists. the unfortunate truth is that the development community. and mold that into a style that you (or your development team) can embrace. model. simply keep in mind that.” .David Heinemeier Hansson¹⁰ It’s your job to take in all of the input and advice around the web. it doesn’t matter how you test…just as long as you do test. There is no spoon. as a whole. The dissonance continues beyond terminology. you’ll make use of multiple styles of testing. In most cases. Don’t worry about this too much. recommend strict adherence to the TDD philosophy: do not write a single line of production code until you’ve first written a test. like Bob Martin (Uncle Bob).com/svn/posts/3159-testing-like-the-tsa . request specs. can’t seem to agree on terminology to save their lives.” .Chapter 1: Test All The Things 20 “Intro to PHPUnit” chapter! For now. that you can then morph to your style. in what order those tests are written is a different story. like system testing. As the saying goes. like DHH (creator of Ruby on Rails). as you develop applications. there’s close overlap between these terms and the ones referenced earlier. ⁹http://www. the most important thing is to get you testing. and more as one person’s adaptation of testing. there are a few different ways to install PHPUnit on your system. PHPUnit uses assertions to verify that the code under test (or SUT. we first need to install a required prerequisite: PHPUnit. what’s this Composer¹² thing? It’s the PHP community’s preferred tool for dependency management.org .Chapter 2: Introducing PHPUnit I get it: you’re a cowboy and want to dive into testing your Laravel applications right now. you have a basic understanding of what Composer is. before moving forward. and how to use it. PHPUnit¹¹ is a unit testing framework. As a Laravel 4 developer. Wait.de/manual/3. • Composer .No one on the planet has ever enjoyed Pear. we’ll take the simplest route.phpunit. pony. created by Sebastian Bergmann. for system under test) behaves as expected.Think of this one as a one-click archive. Similar to other frameworks. It provides an easy way to declare a project’s dependencies. hold on there. and pull them in with a single command. Whoa.8/en/index. it’s vital that. You’re certainly free to choose the method you wish. Installation At the time of this writing.Installs PHPUnit on a per-project basis.json file of our Laravel application. • PHAR . however. Before we can dig into Laravel 4’s excellent testing facilities. and add PHPUnit as a development dependency to the composer. including: • Pear . ¹¹http://www.html ¹²http://getcomposer. as we’ll heavily be leveraging Laravel and Composer in this book. and.7. This is the best way to learn as quickly as possible. you should now have access to both vendor/bin/phpunit and vendor/phpunit/. we don’t need to worry about the production server pulling in this package. libraries. Go ahead and try it out by viewing a list of PHPUnit’s commands. if all goes according to plan. The next step is to install it and any other dependencies that you might have registered in your application.*" } 2 3 4 5 } Notice how we’re not adding the dependency to the require object. and append the following: 1 { "require-dev": { "phpunit/phpunit": "3. From the root of your project. run: 1 composer install --dev Give that command a moment to process. assuming that Composer is installed globally on your system. called lesson.json file. always strip the examples down to the bare essentials. From the command line.Chapter 2: Introducing PHPUnit 22 Before we dive into a Laravel 4 project. Create a new folder. Within it. When learning new technologies. or frameworks. As PHPUnit is only necessary during development. run: 1 vendor/bin/phpunit -h . let’s begin with the absolute basics. add a new composer. zshrc. While installing PHPUnit directly through Composer is certainly a valid route . It makes zero difference. Making Packages Available Globally The method outlined in the previous section requires PHPUnit to be installed through Composer for each new project.and will do just fine for the purposes of this book . Don’t forget that aliases are your friend. but can easily be added to ∼/. This is a temporary alias. a basic understanding of these various commands can carry you a long way. try: alias t="vendor/bin/phpunit". To call PHPUnit with a single key.some developers prefer to stick with Pear (I have no idea why). so that you can simply type phpunit. To allow for this. don’t worry. add its companion composer. 1 export PATH=$PATH:/Users/Jeffrey/composer-packages/vendor/bin With this technique. ¹³https://github. Even better. Add vendor/bin to your path. within your computer’s home directory (or anywhere. This can be added to your ∼/.7.Chapter 2: Introducing PHPUnit 23 If that output feels overwhelming.json file. If you prefer.bash_profile or ∼/. and list your desired global dependencies within the require-dev object. composer-packages. feel free to do so. Here’s an example for my Unix-based system. PHPUnit will always be available.bash_profile. you can alternatively create a master composer project for packages that should be available globally.com/robbyrussell/oh-my-zsh . on a Mac. 1 { "require-dev": { "phpunit/phpunit": "3. really). If you fit that description. create a new folder. if you use oh-myzsh¹³. Tip: You certainly don’t want to type vendor/bin/phpunit every time you need to run your tests.*" } 2 3 4 5 } Next. updating these global packages only requires a simple composer update. add the full path to the project’s vendor/bin directory to your path. Like Git. 1 assertion). you’ll be introduced to your first PHPUnit assertion. World. 1 <?php // lesson/tests/PracticeTest. . though.'. keep things simple. For now. PHPUnit will tunnel through this folder recursively. you’ll find a big fat F. for failure. of course.'). When you add a second test. OK (1 test. then two periods will be displayed… if they both pass. Memory: 2. run the test to ensure that it works as expected. 3 4 .18 by Sebastian Bergmann. 8 } 9 10 } Before we decode it. 7 $this->assertTrue($greeting === 'Hello.php 2 3 4 5 6 class PracticeTest extends PHPUnit_Framework_TestCase { public function testHelloWorld() { $greeting = 'Hello. searching for test files. 1 2 $ phpunit tests PHPUnit 3. notice the final line.Chapter 2: Introducing PHPUnit 24 Assertions 101 It’s time for the “hello world” of testing.php. Here’s some boilerplate to get you started. This. Add a new file. In the event that the test fails. called PracticeTest. and store all test files in the same space. First.50Mb 7 8 OK (1 test. 1 assertion) Because we have not yet applied any configuration options. World. In this section. instead of a period. This means that you’re free to organize the tests folder structure however you wish. to a tests folder within your project.7. reveals that one test passed successfully. we must specify a path to where the tests for our project are located. assertTrue. 5 6 Time: 0 seconds. to state the obvious. Did you notice the single period in the output? That’s the representation of a single test. php:8 The majority of your days will be spent staring at failing tests.php 2 3 4 5 6 class PracticeTest extends PHPUnit_Framework_TestCase { public function testHelloWorld() { $greeting = 'Hello. 13 14 /Users/Jeffrey/Desktop/chapter-1/tests/PracticeTest.18 by Sebastian Bergmann. World. World. Until then.'. we can see that there was one failure from the testHelloWorld test. use the --colors option to request colored output: vendor/bin/phpunit --colors tests. 1 <?php // lesson/tests/PracticeTest. World. 8 } 9 10 } . 7 $this->assertTrue($greeting === 'Hello. To refresh your memory. we’ll dig more deeply into PHPUnit’s configuration options. Decoding A Test Class Structure Even though this is a simple class. Helpful! Where Are The Colors? In the next chapter. Memory: 2. so get used to it! Luckily. the output will prove incredibly helpful. 3 4 F 5 6 Time: 0 seconds.Chapter 2: Introducing PHPUnit 1 2 25 $ phpunit --colors tests PHPUnit 3. here’s the code once again.7. there are a handful of important bits and pieces to be aware of.75Mb 7 8 There was 1 failure: 9 10 11 12 1) PracticeTest::testHelloWorld Hello. and specifically on line eight. Above. Failed asserting that true is false.'). Every test should be contained within a method that has a descriptive name. • Inheritance . inverse methods are nearly always available. then assertFalse has you covered. Never ignore test readability. As you’ll find in PHPUnit. but false. not true. and begins with the word. Let’s introduce another assertion that is a better fit for this task: assertEquals. While assertTrue will get the job done. Convention over configuration. as well as pull in a few helper methods for testing our applications. In this case. This class was made available when we installed PHPUnit through Composer. equal to “Hello. 1 $this->assertFalse(ACTUAL. OPTIONAL MESSAGE). You’ll soon find. If you move up the inheritance chain. that. it’s not the most readable option in this case. We simply inherit from TestCase to set up some Laravelspecific processes. assertEquals In the previous example. It’ll eventually bite you. and commit some of these techniques to memory. World. you’ll find that PHPUnit’s test assertions are quite readable! Even without understanding the nuts and bolts.The name of the class is the same as the file name. Hopefully.php convention. All assertions are available on the test class instance. if you do. $greeting). assertTrue 1 2 $greeting = 'Hello. • Method Naming . OPTIONAL MESSAGE). we typically extend Laravel’s TestCase (the other file within the tests/ folder).'. While this can be modified.The class extends PHPUnit_Framework_TestCase. itself. however. let’s stick with the defaults for now. you’ll find that a parent class does extend PHPUnit_Framework_TestCase. Take a few moments. when testing in Laravel. Notice that we’re following a FooTest. right? • Matching . in fact.” The assertTrue method accepts two parameters. $this->assertTrue($greeting == 'Hello.Chapter 2: Introducing PHPUnit 26 • File Naming .'. it’s easy to decipher what’s happening here. test. 1 $this->assertTrue(ACTUAL.The file name is important. World. we want to assert that the value of the $greeting variable is. our only goal was to assert that a variable’s value was equal to a specified string. Should you need to assert that a value is. World. . Let’s next investigate the test logic. $sum. World. For bonus points. OPTIONAL MESSAGE). equal to 2 + 2. and has the same signature. Next. clearly. spin three times. Run the tests by returning to the command line and calling PHPUnit: 1 $ phpunit Success! Do a happy dance. If you wish to prove that two values are equal to one another. then. ACTUAL. That’s better. how might you assert that a variable is equal to 0? You might try: . write the assertion first. assertEquals accepts three arguments: 1 $this->assertEquals(EXPECTED. $this->assertEquals('Hello.'. in fact. even though both will work. write an assertion to prove that $sum does. Your Turn: Make a variable. equal 4.Chapter 2: Introducing PHPUnit 1 2 27 $greeting = 'Hello. assertNotEquals is the inverse of this assertion. For instance. and continue on. isn’t it? Similar to most of PHPUnit’s assertions. assertEquals is a better choice than assertTrue. $greeting). assertSame assertEquals will break down as soon as you need to compare with strict equality.'. World. // true In situations. should you substitute any other falsy value. $val). Imagine that you have a list of names. PHPUnit is heavily documented. In the instances where a piece of code requires a different form of assertion. 'Shawn'. $val). the tests will still return green. While PHPUnit offers dozens of assertions. 'Dayle']. HAYSTACK. and need to prove that the array contains a specific value. it’s important to cover the essentials. 1 2 3 4 5 public function testLaravelDevsIncludesDayle() { $names = ['Taylor'. reach for assertSame. $this->assertSame(0. $this->assertContains('Dayle'. $this->assertSame(0. when you require strict comparison (or effectively ===). however. you’ll likely find that even a handful of them will serve the huge majority of your testing needs. $val). it’s better to opt for the more readable option: assertContains. however. $val). . $this->assertEquals(0. } The signature of assertContains is: 1 $this->assertContains(NEEDLE.Chapter 2: Introducing PHPUnit 1 2 28 $val = 0. // true assertContains The goal of this book isn’t to teach every assertion. while assertTrue can handle this task. OPTIONAL MESSAGE). Again. $names). This will work. $this->assertEquals(0. 1 2 $val = null. as demonstrated below: 1 2 $val = null. // false 3 4 5 $val = 0. // Failed asserting that an array cont\ ains 'parents'.Chapter 2: Introducing PHPUnit 29 An easy way to remember the argument order for these various assertions is to read them aloud. 'Suzy'] ]. // true assertArrayHasKey There will be times when you need to assert that a provided array contains. Let’s ensure that a troll is not included in our list of influential Laravel developers. // true 8 9 } To take things a step further. $family). assertContains is not the right tool for the job. 'children' => ['Timmy'. 'Suzy'] ]. we need to assert that a key exists within the specified array. 1 2 $this->assertContains('parents'. The solution is assertArrayHasKey. the inverse assertion is also available. $family). what if we expect the parents key to contain an array of one or more parents? How might we do that? . not a specific value. $this->assertNotContains('Troll'. let’s assume that we require a $family to contain a parent. “Assert contains Dayle. In situations such as this. $names). 'Shawn'. 'children' => ['Timmy'. 7 $this->assertArrayHasKey('parents'. given this HAYSTACK” is more readable than “Assert contains HAYSTACK. In this pseudo-example. 1 2 $names = ['Taylor'. Dayle. but a key. and the item. Instead. For instance: 1 2 3 4 $family = [ 'parents' => 'Joe'. 1 2 3 4 5 6 public function testFamilyRequiresParent() { $family = [ 'parents' => 'Joe'.” As you are now aware. 'Dayle']. you will need to ensure that a variable is an instance of some class. 7 $this->assertInternalType('array'. $this->assertInternalType('integer'. we might do: 1 2 3 4 5 6 public function testFamilyRequiresParent() { $family = [ 'parents' => 'Joe'. // true assertInstanceOf Often. given the following code: . Continuing on with the family example. MESSAGE). 1 $this->assertInstanceOf(EXPECTED. $age). For a usage example. ACTUAL. via the assertInstanceOf method. 'children' => ['Timmy'. This is easy in PHPUnit. 'Suzy'] ]. 1 $this->assertInternalType(EXPECTED. to assert that a variable is of type integer: 1 2 $age = 25.Chapter 2: Introducing PHPUnit 30 assertInternalType assertInternalType can be used to verify the type of the supplied variable. $family['parents']). MESSAGE). // false 8 9 } Or. ACTUAL. to assert that a family’s parents key is equal to an array of one or more items. If one is not. } 9 10 11 12 13 } To ensure that $stamp is an instance of PHP’s DateTime class (other than the fact that we’ve provided type hints). if you use too many conditionals. // true 5 6 } Asserting Exceptions When unit testing. } 4 5 6 7 8 public function getStamp() { return $this->stamp. then the test will fail. This is one of the core reasons why. it’s important to test every possible path through your code.Chapter 2: Introducing PHPUnit 1 2 31 class DateFormatter { protected $stamp. 3 public function __construct(DateTime $stamp) { $this->stamp = $stamp. $date->getStamp()). Let’s say that a method should throw an exception if a non-numeric value is passed. given the contents of the method that it corresponds to. In PHPUnit. Here’s how we might assert that: . we use doc-blocks to assert exceptions. like so: 1 2 3 /** * @expectedException EXCEPTION_NAME */ This doc-block declares that. it can make a class or method difficult to test. 4 $this->assertInstanceOf('DateTime'. we could use the following test: 1 2 3 public function testStampMustBeInstanceOfDateTime() { $date = new DateFormatter(new DateTime). PHPUnit should expect an exception to be thrown. An example of such a path might be one that throws an exception. Instead. and they burst.Chapter 2: Introducing PHPUnit 1 2 3 4 5 6 7 8 32 /** * @expectedException InvalidArgumentException */ public function testCalculatesCommission() { $commission = new Commission. reviewing every available PHPUnit assertion. $commission->setSalePrice('fifteen dollars'). there is no assertX call. fill them up too quickly. we’ve merely added the necessary code that should make the class throw an exception. Next. Summary While we could continue on our way. before we dive into Laravel-specific testing. . we should take some time to configure PHPUnit more fully to our needs. When you do need additional functionality. the documentation is only a click away. this would be unwise. The handful of assertions covered in this chapter should be enough to get you on your way. Our brains are leaky little things. in this case. } Notice how. we’ll discuss a selection of the most helpful options. 1 phpunit --colors . Options Technicolor Though you’re free to live life in black and white. In this chapter. it’s recommended that you instead enable colored output when running tests. a simple green versus red output can help to speed up your development workflow.Chapter 3: Configuring PHPUnit PHPUnit ships with a vast array of configuration options . We respond to color more quickly than text.many of which you’ll never use. and then review the process of creating a custom configuration XML file to store these settings. Similar to a traffic stop light. Though you could manually require this file either at the beginning of every test class or within a master file. is Perl’s text-based interface between testing modules.Chapter 3: Configuring PHPUnit 34 Bootstrapping There will be times when you need to include certain files before executing your tests.php) is included before any tests run. it’s better to leverage PHPUnit’s bootstrap switch. an acronym for Test Anything Protocol.php" Output Formats In addition to the default format for test reporting. like so: 1 phpunit --bootstrap="vendor/autoload. TAP. . A perfect example of this is ensuring that Composer’s autoload script (located at vendor/autoload. PHPUnit offers two alternative flags: --tap and --testdox. using some options that you haven’t yet seen. See below: 1 2 $ phpunit --testdox tests PHPUnit 3. you may now exclude the --colors flag from your tests.2 The TestDox format takes a slightly different approach. Don’t do this: 1 phpunit --bootstrap="vendor/autoload.xml file and insert: 1 <phpunit colors="true"></phpunit> With that single line. create a phpunit. testRedirectsToHomePageOnSave will be converted to Redirects To Home Page On Save.PracticeTest::testHelloUniverse 1. As an example. However. .7.PracticeTest::testHelloWorld ok 2 . 3 4 5 6 Practice [x] Reloads current page if save fails [x] Redirects to home page on save This can be helpful for a birds-eye view of which tests were successful. Remember.Chapter 3: Configuring PHPUnit 1 2 3 4 5 35 $ phpunit --tap tests TAP version 13 ok 1 .. leverage PHPUnit’s ability to read a configuration file.php" --testdox --colors tests/ Instead. let’s flesh this file out a bit more. Within the root of your project. then you’re doing it wrong. as well as providing documentation. that’s one of the significant advantages to writing tests: free documentation! XML Configuration File If you find yourself passing multiple switches with every call to phpunit. It will read your test methods and convert them from camelCase names to readable sentences. Let’s review the simplest possible example.18 by Sebastian Bergmann. we still must manually specify the path to the tests/ directory. Let’s add another test suite that is specifically for.Chapter 3: Configuring PHPUnit 1 2 3 4 5 6 7 36 <phpunit bootstrap="vendor/autoload. Also. as I see it. The remainder are optional. . and warnings be converted to exceptions. notices. like so: 1 2 3 4 5 6 <phpunit bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" stopOnFailure="true"> 7 8 9 10 11 12 13 <testsuites> <testsuite name="Test Suite"> <directory>tests</directory> </testsuite> </testsuites> </phpunit> Splitting your test suites in this way can add a great deal of flexibility down the line. there’s no use in executing potentially hundreds of tests if you’re only concerned with fixing the first failure. when you only wish to test a particular subset of your application. if a test fails. That’s no good! To set a custom directory. As things currently stand. Though some may disagree. Think of it as Control + C on failure. integration tests.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" stopOnFailure="true"> </phpunit> The first two attributes should already be familiar to you. then I want to cancel any remaining tests. I tend to prefer that all errors. use the <testsuites> element along with any number of child <testsuite> elements. and boil down to personal choice. say. while Windows faithfuls can use the simple Ruby Installer¹⁵. in addition to the unit tests. don’t offer this functionality out of the box (most IDEs do). Continuous Testing An excellent way to get into the testing groove is to automate the process of running your tests each time an applicable file is saved. Nonetheless.tutsplus. for real-world projects. to leverage the following tools. Success! Did You Know? Laravel ships with its own phpunit.xml file (located in the root of your application). you’ll find that the default settings are perfect for your needs. Mac users can follow this guide¹⁴. like Sublime Text.org/ . you’ll need to install Ruby and Rubygems. On save. however. to execute all tests within the tests/ folder while passing the options listed above. While light-weight editors.com/tutorials/ruby/how-to-install-ruby-on-a-mac/ ¹⁵http://rubyinstaller. As such. manually creating this file is only necessary for non-Laravel projects. be sure to organize your tests. you don’t want to be executing acceptance tests.Chapter 3: Configuring PHPUnit 1 2 3 4 37 <testsuites> <testsuite name="Test Suite"> <directory>tests</directory> </testsuite> 5 6 7 8 9 <testsuite name="Integration"> <directory>tests/integration</directory> </testsuite> </testsuites> To execute only the tests referenced in the “Integration” test suite. Please note that. Though it may freely be modified. ¹⁴http://net. most of the time. it’s a fairly simple process to install a few command-line tools to get the job done. run: 1 phpunit --testsuite="Integration" For most chapters in this book. we’ll stick with one master test suite. simply run phpunit. INFO . It’s possible that you’ll be advised to install additional packages.Chapter 3: Configuring PHPUnit 38 RubyGems¹⁶ is the Ruby community’s gem hosting service. not dissimilar to Composer’s Packagist companion.org ¹⁷https://github. along with the notification tool that best suits your needs and OS.com/guard/guard ¹⁸https://github. running this command will generate a file. called Guardfile. such as rb-fsevent. if you desire notifications (essentially a PASS/FAIL banner on save).INFO . you’re all set to go! Initialize Guard for your application by running: 1 guard init 2 3 4 19:15:57 . follow orders! 1 gem install rb-fsevent Lastly. If so. containing the following boilerplate: ¹⁶http://rubygems. feel free to edit it Assuming that guard-phpunit is the only Guard plugin that you have installed. Mac users should install the Terminal Notifier Guard¹⁸ gem. 1 gem install terminal-notifier-guard With that. Definition: Guard is a command line tool to easily handle events on file system modifications. 1 2 gem install guard gem install guard-phpunit Pay close attention to the output upon running these commands. written in Ruby.Writing new Guardfile to /Users/Jeffrey/Desktop/demo/Guardfile 19:15:57 . To get setup. I recommend using Guard¹⁷ to handle your continuous testing needs. If you like the idea of automated testing.phpunit guard added to Guardfile. begin by installing Guard and the PHPUnit plugin through the command line.com/Springest/terminal-notifier-guard . php$}) end If you’re new to Ruby. You must tell it when to begin watching files. :tests_path => 'tests' do watch(%r{^.+Test. :cli => '--colors' do watch(%r{^. 1 2 3 guard 'phpunit'. don’t let this code scare you. :cli => '--colors'.php. } } Now Guard doesn’t magically run automatically. any file in the root of the project that ends with Test.INFO 19:41:15 . in human speak. Guard uses TerminalTitle to send notifications. and add: 1 <?php // tests/ExampleTest.INFO 19:41:15 .com/guard/guard#readme 3 4 5 6 guard 'phpunit'. It specifies that we want Guard to watch all files that match the regular expression. Let’s adjust this somewhat to reflect a more realistic folder structure. Running all tests F Failures: 1) ExampleTest::testContinuousTesting Failed asserting that false is true. using the guard command. it’s really quite simple.php file. we’ve specified that the tests/ path should be used when triggering all tests. ˆ. Give it a try: create a simple tests/ExampleTest.+Test. or. .INFO 19:41:15 .+Test\.php$.Chapter 3: Configuring PHPUnit 1 2 39 # A sample Guardfile # More info at https://github.php 2 3 4 5 6 7 8 class ExampleTest extends PHPUnit_Framework_TestCase { public function testContinuousTesting() { $this->assertTrue(false).php$}) end Using the tests_path option.INFO > [^C5AD6455F02F] > [^C5AD6455F02F] > [^C5AD6455F02F] > [^C5AD6455F02F] > [^C5AD6455F02F] Guard uses TerminalNotifier to send notifications. 1 2 3 4 5 6 7 8 9 10 $ guard 19:41:15 . hit Enter.php$}) 3 4 5 watch('app/libraries/Calculator. :cli => '--colors'. Make a few changes to tests/ExampleTest.php. type exit. Watching Files Chances are high that you want Guard to monitor more than merely test files. . 1 2 guard 'phpunit'. Alternatively.php. If following a TDD cycle.php. to re-run all tests. or simply e. and see for yourself. :tests_path => 'tests' do watch(%r{^.php is modified. is modified. then run.+Test\.php and CalculatorTest. 1 failures If everything went according to plan. ˆ. save. However.php'} end To verbalize this new line of code: when Calculator. If you return to the command line. Continuous testing for the win! As long as a file within the tests/ folder that matches the regular expression.Chapter 3: Configuring PHPUnit 11 12 13 14 > > > > 40 [^C5AD6455F02F] # /Users/Jeffrey/Desktop/demo/tests/ExampleTest. 1 assertion. to cancel Guard. we clearly don’t want to repeat this step for every single file in our project! Let’s instead use regular expressions to make the matching more dynamic. Here’s an updated Guardfile that creates a link between Calculator.3. but specifically the one at tests/libraries/CalculatorTest.php') { 'tests/libraries/CalculatorTest.php$.+Test.php:6 [^C5AD6455F02F] [^C5AD6455F02F] Finished in 0 seconds [^C5AD6455F02F] 1 test. you’ll see something along the lines of Figure 2. you’ll surely need a way to trigger a class’ associated test upon save. the tests will run again. not all tests. php. :tests_path => 'tests' do watch(%r{^.glob('tests/\**/*Test. This takes care of our library tests.php}) { |m| "tests/libraries/#{m[1]}Test. :cli => '--colors'. For example. there likely won’t be any dedicated test file to execute. In these situations.php is saved. until the Guard PHPUnit plugin offers a better solution. :tests_path => 'tests' do watch(%r{^.Chapter 3: Configuring PHPUnit 1 2 41 guard 'phpunit'. Guard will now attempt to run the test located in tests/models/UserTest.php$}) 3 4 5 watch(%r{^app/views/. Triggering Multiple Files There will be times when you wish to execute all tests for your app. Guard will trigger all *Test. is saved. it’s easiest to instead trigger all tests within a given folder. It’s admittedly not the most elegant solution. .php" } end Now. however.php') } end With this last modification.glob() to return an array of all appropriate files. this is the best option.+). :cli => '--colors'. :cli => '--colors'. 1 2 guard 'phpunit'. we’re using Dir.+)/(.php}) { |m| "tests/#{m[1]}/#{m[2]}Test.+Test.+Test.+). if any file within the app/views folder is saved. any file within the app/libraries directory will trigger its associated test.php files within the tests/ directory.+$}) { Dir. When the file. to extend it even further. Above. This is widely considered to be a best practice.php.php$}) 3 4 5 watch(%r{app/(. if a particular file is saved.+Test. if app/routes. we might update the Guardfile to: 1 2 guard 'phpunit'. the subfolder matching will be dynamic as well. :tests_path => 'tests' do watch(%r{^. but.php" } end With this modification. Tip: Notice how the folder structure for our tests mirror the app structure. app/models/User.php$}) 3 4 5 watch(%r{app/libraries/(. that. I take a different approach and manually trigger my tests. 1 nmap .php<cr> Now. Chances are. Though this works. In these cases. This way. this makes for the cleanest TDD cycle . Guard isn’t limited to PHPUnit by any stretch. 1 nmap . While the previous command is excellent for triggering the full test-suite. and much more. I use a few mappings when doing TDD. ¹⁹https://github. as a Vim user.t :!phpunit<cr> This specifies that.Chapter 3: Configuring PHPUnit 42 Tip: Remember. Unless you’re a Vim user. Don’t forget that you can press the up arrow key to cycle through your previous commands. Success! You now have a simple key stroke for testing the class. reload the browser.php.com/guard/guard/wiki/List-of-available-Guards . it assumes that the mapping is triggered from the test class. when testing in isolation. feel free to continue on to the next section. it’s better to exclusively target the applicable class. running . for my workflow. though. Refer to the Guard Wiki¹⁹ for a full list of available plugins. you don’t have to manually type the same sequence over and over. As a Vim user. It can additionally be used to compile Sass and CoffeeScript. you’ll be using split panes (multiple windows).t.t :!phpunit app/tests/MyClassTest.t :!phpunit %<cr> The only difference with this mapping is that PHPUnit will exclusively test the current class (represented by the percentage sign). I’ve found that. A first option might be: 1 nmap .one that I’m in complete control over. when I type .t.t will correctly trigger the tests located in MyClassTest. Some Vim-Specific Advice Though plenty of developers prefer automated testing on save. the phpunit command should be executed (<cr> represents a carriage return). regardless of which split pane you might be in. I manually create mappings on the fly to override the default behavior of . A better approach is to hardcode the file that you’re currently working on. edit the file as needed. And.Chapter 3: Configuring PHPUnit 43 Summary In this chapter. an understanding of these various options will prove essential. don’t forget that the framework provides its own phpunit.xml file out of the box. with that. Go have some celebratory raisins. when testing Laravel applications. However. . you’re doing great. you were introduced to the basics of configuring PHPUnit. as noted earlier. For language-agnostic projects. This file is by no means read-only. we’re on to the next chapter. If you need to add a new test suite (perhaps for your acceptance tests). Chapter 4: Making PHPUnit Less Verbose Importing Assertions as Functions There’s no denying that $this->assertEquals can get verbose. 4 5 6 7 8 class PracticeTest extends TestCase { public function testAdd() { $sum = add(10. in effect. This. allows you to remove the $this portion. 10 } 11 12 } .php'. If you installed PHPUnit through Composer. you can import all of the assertions as simple functions. then the list of functions will be stored in: 1 vendor/phpunit/phpunit/PHPUnit/Framework/Assert/Functions. 5). Though we won’t be using this technique throughout the book.php Simply require that file at the top of your test class (or at the top of Laravel’s TestCase). if you wish. 1 2 $name = 'Doug'. 9 assertEquals(15. assertEquals('Doug'. and you may now reference these assertions as global functions. when you have to write it repeatedly. $name). $sum). 1 <?php 2 3 require_once 'vendor/phpunit/phpunit/PHPUnit/Framework/Assert/Functions. View::make(). Behind the scenes. if you’d like to use it in your own projects. Finally. This allows for easy-to-use code. This is why I recently released two wrappers classes²⁰ around PHPUnit’s assertion library that bring the Laravel feel to your testing: Should and Assert.json to reference the phpunit-wrappers package. while instantiating the applicable classes and injecting any dependencies behind the scenes. the applicable PHPUnit assertions are still being called. 21). 2 + 2). Validator::attempt(). import the Should or Assert class (or both) into your test file. Coding is complicated enough. 3 4 Assert::greaterThan(20. your assertions will take the form of: 1 2 Should::equal(4. Should::beTrue(true). Laravel appeals to you so much because it can be read like simple English. run composer install --dev or composer update --dev to download the package from GitHub into your vendor directory.com/JeffreyWay/Laravel-Test-Helpers . we’re still limited to PHPUnit’s verbose syntax that doesn’t blend with Laravel as elegantly as we might hope. they would realize that facades allow for a static-like API. we don’t need to introduce unnecessary jargon to make the process even more confusing! Though the misinformed will criticize Laravel for using too many statics. like so: 1 { "require-dev": { "way/phpunit-wrappers": "dev-master" } 2 3 4 5 } Next. With these two wrappers. which can be downloaded through Composer. when it comes to testing. The only problem is that. Form::open(). Though I won’t be using this package in this book. update your composer.Chapter 4: Making PHPUnit Less Verbose 45 Applying the Laravel Style to PHPUnit Chances are. if they would take a closer look. and have fun! ²⁰https://github. These wrappers simply provide a more expressive interface for writing tests. such as: 1 2 3 4 Redirect::route(). Let’s stick with the standard API. when you call Should::eq(). . doing so is a cinch. Now. 12 13 } 14 15 } If you want to create a custom alias for a PHPUnit assertion. Assert::equals('Joe'. Nifty! Please note that. while I favor many of the tricks outlined in this chapter. behind the scenes. use Way\Tests\Should. $name). the arguments that you pass to it will be sent through to PHPUnit’s native assertEquals method.Chapter 4: Making PHPUnit Less Verbose 1 46 <?php 2 3 4 use Way\Tests\Assert. 11 Should::equal('Joe'. for the purposes of the book. 1 2 3 Should::getInstance()->registerAliases([ 'eq' => 'assertEquals' ]). 5 6 7 8 9 10 class PracticeTest extends TestCase { public function testItWorks() { $name = 'Joe'. $name). they won’t be used. each which exercises your application in slightly different ways. where the authors seemed to ignore this rule. and as a first step into the testing waters. it’s really quite simple: we’re still figuring this stuff out! Even though the concept of testing software goes as far back as the seventies . in many ways. unit). let’s focus exclusively on one object at a time. back then. think I had a decent grasp on the terminology and rules.perhaps ones that do hit databases and query APIs . As such. Imagine learning what two plus two equals. As I continued digging in. even today. The differing view points were only the beginning of my struggles. preached an entirely different set of guidelines. we took the first steps toward “slow and steady wins the race” testing. though.during the days of Smalltalk²¹ . which language it was written for. in favor of touching every part of the system. Of course.rest assured that they absolutely have their place. Even worse. while the Ruby on Rails community is a culture that has widely embraced and evangelized testing. and then move on to the next article that. I was dumbfounded and overwhelmed. But. though. I’d read one article. the same unfortunately can’t be said for the PHP world. the development community remains divided on many issues.org/wiki/Smalltalk . As a student. but. we’ll focus exclusively on unit testing with test-driven development. I hope you’re on board! In this chapter. be prepared to discover different methodologies and terminologies. including such basic things as whether testing is beneficial. ²¹http://en. including the article’s publish date. now. As a result. right? The tides are turning.Chapter 5: Unit Testing 101 In Chapter 1. I suppose what I want you to understand and appreciate is that we’re still in the early days of testdriven development. and testing in general. My Struggles One unfortunate truth of learning how to test is that so much of the information on the web and in books contradicts one another. I’d learn from one source that testing in isolation was paramount. In fact. you’ll have a number of test suites. dependent upon a number of factors. we’ll get to them in due time.the truth is that.wikipedia. If you’ve come across other types of testing in your web travels . and then move on to find countless other articles. in real world projects. when all the resources provide different answers? What’s the reason for this dissonance in the community? Well. For now. we’re fixing that. I realize that these articles focused on different types of testing (acceptance or functional vs. Let’s continue on this path. as you continue learning beyond this book. That being said. I didn’t yet understand or appreciate the various types of testing. and what belief system its writer has. this chapter is not concerned with those other styles of testing. This is referred to as testing in isolation. Given this set of data. $this->assertEquals($expected. Then. 'Matthew'. however. 3. Nothing beyond that class should be touched (if we’re following the London School of TDD). As you’ll find. Assert Preparing a unit test can be reduced to three simple actions. Arrange. not our models or controllers (we’ll get to those soon). instantiate objects. 'Dayle'. If I feed a method this set of data. not a single dependency. pass in mocks. when I perform this action.Chapter 5: Unit Testing 101 48 Unit Testing Okay. 1. Assert: Compare your expected output to what was returned. Arrange: Set the stage. 'Neil']. it’s time to dig into the process of unit testing. I expect that. Not the database. 9 10 11 12 } If you pick up a BDD-specific book. Unit testing refers to the smallest testable piece of an application. you might come across slightly different terminology that translates to the same thing: Given. 6 7 8 // Assert $expected = ['Taylor'. $result). every test follows this basic flow. 5 // Act $result = array_until('Matthew'. This will give us the perfect excuse to learn more about proper test organization. $names). then I expect that response. Here’s an example: 1 2 3 4 public function testFetchesItemsInArrayUntilValue() { // Arrange $names = ['Taylor'. but simple functions and utility classes. then. 'Shawn'. 'Dayle']. Act: Execute the thing that you wish to test. 2. When. in return. Here’s a second test for illustration purposes: . Act. You shouldn’t hit a web service.Chapter 5: Unit Testing 101 1 2 3 4 5 6 7 49 /** * @expectedException InvalidArgumentException */ public function testThrowsExceptionIfValueDoesNotExist() { // Given this set of data $names = ['Taylor'. when unit testing a model. 10 11 } Testing in Isolation A core fundamental to successful unit testing is to test in isolation. it’s a good indication that refactoring is in order. Instead. } 5 6 7 8 9 return array_slice($arr. 4 if (false === $index) { throw new InvalidArgumentException('Key does not exist in array'). here’s the code to make both tests pass: 1 2 3 function array_until($stopPoint. if you’re curious. $index). If the ratio becomes lopsided. and encourages you to describe the behavior of your code. $arr). 'Shawn'. You can flex multiple parts of the system in the future. This means that all outside dependencies which aren’t directly related to the thing that you’re attempting to test should be stubbed or mocked (more on these terms later). . 'Neil']. be hitting the database. Also. you shouldn’t. 8 // When I call the until function and // specify a different value $result = array_until('Bob'. when you write integration tests. 9 10 11 12 // Then an exception should be thrown (see doc-block) 13 14 } Though the terminology is different. Finally. 0. $names). You shouldn’t even reference one of your other classes. 'Matthew'. $arr) { $index = array_search($stopPoint. stick with one object at a time. in the process. this Given/When/Then syntax is a bit more readable. 'Dayle'. note that each step in the cycle is quite small. For example. or test-driven development. TDD declares three core rules: 1. For example. The two are not mutually exclusive. With this style of coding. intentional development process. Or. then the feature has been implemented. Once you’ve written the minimum amount of code to make that behavior work. In these situations. and then continue to depend upon that same state for assertions in future tests. you may only write the minimum amount of code to make the test pass . but fails when triggered by itself. Refactor: Only when the tests have passed (they return green) may you refactor your code.then chances are. Approach each failing test from the perspective of.by describing the behavior of your code . you’ll often hear about a seemingly different form of testing. While a cowboy approach may once have been the norm in our industry. in addition to ensuring that your code works as expected. Make it Pass: Once a test has been defined. Don’t do this. as a collective. Test-Driven Development Test-Driven Development is an agile software pattern in which a developer prepares a test before a single line of production code is written. One is merely the other in its best form. Tip: Each test should recreate the world in which it acts upon. Popularized by Kent Beck. An easy way to detect this is if a particular test passes when the entire suite is executed. As it turns out. the failure is due to the test’s dependency on some previously set state. never depend on test order.even if this means faking a method’s return value. this methodology forces you to think before coding. Write a Failing Test: You may not write a single line of production code unless a failing test is present. if you write TDD well . you describe how the SUT (system under test) should behave. you’re following the rules of BDD. when I refer to TDD. What you want to strive for is tests which can easily be executed independently from one another without causing failure. beginning developers will often set state in one test. In this book. Not good. “what is the simplest way to make this test pass?” 3. it’s an anti-pattern. we’re rapidly moving away from it.Chapter 5: Unit Testing 101 50 Tests Should Not Be Order-Dependent Another rule of unit testing is that your tests should never be dependent upon previous tests. referred to as behavior-driven development. in other words. . 2. in favor of a mature. I’m also referring to BDD. Behavior-Driven Development In addition to TDD. Remember that we shouldn’t be searching for “localhost:8000. Laravel 4 recently implemented this very function. this is beginning to make sense. and write the test first.” when running command line tests. Additionally. we’ll test a helper function for a Laravel application that should generate an anchor tag. you’ll be testing logic from external sources. A bit robust. Nonetheless. To take a first step into these waters. called link_me_to. In fact. in other words. how would you want to achieve this functionality in a perfect world? Write it as such. 6 7 } Hopefully. or web service. we write the assertion and run the test. let’s rebuild it for education purposes. 1 $expect = "<a href='http://:/dogs/1'>Show Dog</a>". you’ll want to rename it to something that doesn’t clash with the existing version. we’re never constrained by existing code. Because link_to is now taken. let’s test-drive a simple function. If working along. we need a link_me_to(URL.Chapter 5: Unit Testing 101 51 Testing Functions Before we tackle the calculator. and then it’s your responsibility to organize the necessary production code to make that possible. we’ll stick with the oddly named. class. Or. . 5 $this->assertEquals($expect. link_me_to. $actual). BODY) function that should generate the HTML string that is stored in the $expect variable above. Notice how we interact with the function before writing any production code. In real-world applications. $expect = "<a href='http://:/dogs/1'>Show Dog</a>". but Laravel (after the publication of this book) implemented the very helper function that we’re about to build. Then. we’ll embrace a TDD (test-driven development) approach. first. This way. 'Show Dog'). 1 2 3 4 public function testGeneratesAnchorTag() { $actual = link_me_to('dogs/1'. such as a function. In this case. like so: 1 function link_me_to() {} 2 3 4 5 6 7 class FunctionsTest extends PHPUnit_Framework_TestCase { public function testBuildsAnchorTag() { $actual = link_me_to('dogs/1'.52 Chapter 5: Unit Testing 101 1 2 $expect = "<a href='http://:/dogs/1'>Show Dog</a>". In this case. There’s our next step! Often. Not sure what to do next? Run the tests. The wonderful thing about TDD is that it turns your coding process into a game.3 . $expect = "<a href='http://:/dogs/1'>Show Dog</a>". 'Show Dog'). you’ll find that it’s helpful to write the class or function under test just above the test class. $actual = link_me_to('dogs/1'. tiny steps. 1 phpunit Fig 1. $actual). and execute the test. an error is displayed: Call to undefined function link_me_to().The first failing test. $actual). 8 $this->assertEquals($expect. Return to the command line. 9 } 10 11 } . and PHPUnit will tell you! TDD is about tiny. 3 4 $this->assertEquals($expect. 'Show Dog'). php" ]. we’ll take the latter approach.php function link_me_to($url. this time. "files": [ . Think of this file as one that contains a variety of simple global functions.Chapter 5: Unit Testing 101 53 Once finished.json { "require": { "laravel/framework": "4. Return to composer. we need to instruct it to load this new file. "autoload": { "classmap": [ "app/commands". Luckily. that’s a cinch.php. though. "require-dev": { "phpunit/phpunit": "3. let’s instead try to embrace autoloading. "app/models".*" }. Composer isn’t a mind reader. Let’s assume that we have a helpers. you can always copy it to the proper file. "app/database/migrations". "app/database/seeds". make simplicity a priority. Tip: In the early testing stages.7. which can be used throughout your application.php file within the app/ folder.0.*" }. Here’s how your Composer file should look: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // composer. $body) { 4 5 } Though we could simply require this file within the test class (and probably should at this early stage). Having said that.json and specify a new files array within the autoload object. "app/tests/TestCase. 1 2 3 // app/helpers. app/helpers. to mimic a real-world scenario. "app/controllers". Begin by creating the file. php to the classmap. Generalize Throughout this book. $body) { return "<a href='http://:/dogs/1'>Show Dog</a>". the best choice is to instead use the files array. If we execute phpunit again. 1 composer dump-autoload -o Tip: When running the dump-autoload command. 1 phpunit 2 3 There was 1 failure: 4 5 6 7 1) PracticeTest::testBuildsAnchorTag Failed asserting that null matches expected '<a href='http://:/dogs/1'>Show Dog</\ a>'. In these situations. however. Aha. as should be done whenever this file is updated. instead. } Slime vs. "minimum-stability": "dev" 21 22 23 } Remember: we can’t add app/helpers. always pass the -o (optimize) flag. only null was returned. Next.php 2 3 4 5 6 function link_me_to($url.php" 19 ] 20 }. slime and generalize. we’ll receive the next step. instruct Composer to dump a new list of all the files that should be autoloaded. Let’s slime it as the first step.54 Chapter 5: Unit Testing 101 "app/helpers. as it’s not a class. so it seems that the test expected the anchor tag string. . as illustrated above. 1 // app/helpers. you’ll frequently see references to the terms. Feel free to use these terms interchangeably. be four. In other words.Chapter 5: Unit Testing 101 55 Slime Definition: Slime is nothing more than a bit of jargon that refers to returning a dummy value for the sole purpose of making a test pass. at which point generalizing will become a necessity. Definition: Think of the term. Though hard-coding a returned value might seem foolish to you. Generalize Clearly. the ultimate goal is to ensure that your code never becomes more complicated than is necessary to make the tests pass. anyone? The solution is to slime it. if you pass two and two. See how that works? Calculating (excuse the pun) when to generalize is something that each developer must determine on their own. and hardcode the returned value. } Once you write a second test . sliming it just might be the answer. The key is to determine when it’s appropriate to generalize. as a way to specify when it’s necessary to remove slime. in fact. like so: 1 2 3 function add() { return 4.a hard-coded value will no longer suffice. More traditionally.perhaps. then the returned value from the function will. sliming is temporary. . this is referred to as faking it. The principles of test-driven development dictate that tiny steps should be taken when coding. A first assertion might be to ensure that. Perhaps an example is in order. what is the simplest way to make a test pass? Initially. in favor of real production code. Imagine a basic add function. generalize. What is the easiest possible solution to this assertion? Anyone. passing three and three . 1 assertion) Did You Know? Laravel. and it passes! 1 phpunit 2 3 OK (1 test. 1 // app/helpers. Let’s take things one step further.php 2 3 4 5 6 7 function link_me_to($url.Chapter 5: Unit Testing 101 56 Making the Test Pass Returning to the link_me_to test. To make this a reality. at this point we have two courses of action when our tests return red (or fail): 1. . return "<a href='{$url}'>{$body}</a>". first add a new test and specify the desired end result. Make it green Let’s remove the slime. } Run the tests. it offers a number of particularly useful array operation commands. such as a class or id. In addition to the helpful url() function used above (simply a convenience that maps to URL::to(). and fetch the URL dynamically with Laravel’s native url function. It might be nice if the link_me_to function offered a way to specify attributes for the anchor tag as well. Change the message 2. $body) { $url = url($url). offers a helpers file. itself. each test should represent but one path through your code. 13 14 15 16 17 $this->assertEquals($expect.Expected +++ Actual @@ @@ -'<a href='http://:/dogs/1' class='button'>Show Dog</a>' +'<a href='http://:/dogs/1'>Show Dog</a>' Got it? Let’s make this new test pass. $expect = "<a href='http://:/dogs/1' class='button'>Show Dog</a>". We’ll skip the sliming this time. fool. 'Show Dog'). 10 } 11 12 public function testAppliesAttributesUsingArray() { $actual = link_me_to('/dogs/1'. $expect = "<a href='http://:/dogs/1'>Show Dog</a>". What’s the next step? Run the tests and find out. 18 } 19 20 } As a basic rule of thumb. 1 phpunit 2 3 4 5 6 7 8 9 1) PracticeTest::testBuildsAnchorTag Failed asserting that two strings are equal. ['class' => 'button']). --. $actual). $actual).Chapter 5: Unit Testing 101 1 57 <?php 2 3 class PracticeTest extends TestCase { 4 public function testBuildsAnchorTag() { $actual = link_me_to('/dogs/1'. 5 6 7 8 9 $this->assertEquals($expect. . 'Show Dog'. 1 2 3 function link_me_to($url. 4 // If the user specified any parameters.Chapter 5: Unit Testing 101 1 58 <?php // app/helpers. $parameters = null) { $url = url($url). and… 1 OK (2 tests. as allowed by the TDD pattern. . then // parse them and append to returned string $attributes = $parameters ? HTML::attributes($parameters) : ''. 9 10 } Running the tests should return one small quirk. 2 assertions) It passes! Now that we have a passing test. 7 // If the user specified any parameters. $parameters = null) { $url = url($url). $body. } } 8 9 10 11 12 13 14 15 16 17 return "<a href='{$url}'{$attributes}>{$body}</a>". $attributes = ''.= " {$attribute}='{$value}'". we can move on to any necessary refactoring. 5 6 7 8 return "<a href='{$url}'{$attributes}>{$body}</a>". 18 19 } Run the tests. I happen to know that Laravel’s HtmlBuilder class (you know it as the HTML facade) offers an attributes static method that will handle the parsing.php 2 3 4 5 6 function link_me_to($url. $body. In this case. Let’s refrain from reinventing the wheel by instead leveraging that implementation. then // parse them and append to returned string if ($parameters) { foreach($parameters as $attribute => $value) { $attributes . Testing Classes The most common introduction to unit testing generally revolves around the creation of a calculator. the process is: • • • • Write a test that defines how you want to interact with your code Watch it fail Write the necessary production code to make it pass Refactor Important: This form of testing conforms more to the Chicago style of TDD.Chapter 5: Unit Testing 101 1 2 3 4 5 6 7 59 1) PracticeTest::testGeneratesAnchorTagAcceptsAttributesArray Failed asserting that two strings are equal. Two plus two will always equal four. there’s a reason why they’re used so frequently (along with car examples). just as TDD requires small steps.Expected +++ Actual @@ @@ -'<a href='http://:/dogs/1' class='button'>Show Dog</a>' +'<a href='http://:/dogs/1' class="button">Show Dog</a>' Luckily. I know what you’re thinking: “I hate calculator testing tutorials! I get it. Nonetheless. though. There are those who would argue that the url function that was triggered inside link_me_to should have been mocked. That’s a non-issue and an easy fix: update the test’s $expect variable. for now. trust me. the HTML::attributes() method simply uses double quotes. By removing the barrier to entry. I . --. Don’t complicate it any more than that. so does the learning process. A calculator consists of basic logic that every one of us knows by heart. you can dedicate your entire focus to learning the assertions. when our test expected singles. Once again. rather than worrying about production code logic. we’re testing link_me_to. There absolutely is value to this approach. And that should be enough to give you a passing test! Congratulations! You just used test-driven development to write a helpful function that can be used within your views. Like I said. That’s all TDD is. But. not url. in this case. nobody builds basic calculators in real life. like so: 1 $expect = "<a href='http://:/dogs/1' class=\"button\">Show Dog</a>". and you’ll learn more about it (London school of TDD) soon. We’ll tackle this in two sections: 1. while the easiest way to include this class is to require it at the top like we did earlier.json file. 1 <?php // Calculator.phpunit CalculatorTest. 1 <?php // CalculatorTest.php should be autoloaded.php within the project root. We’re not worried about folder organization for this simple demo. and add a CalculatorTest. and specify that Calculator.php file with the first test. How can we leverage polymorphism to allow for a more extensible calculator? This section will be framework-agnostic. so.Chapter 5: Unit Testing 101 60 feel your pain. What is the simplest way to handle basic arithmetic? 2.php 2 3 4 5 6 7 8 class CalculatorTest extends PHPUnit_Framework_TestCase { public function testInstance() { new Calculator. let’s stay in the habit of embracing Composer for autoloading classes. padawan! Unit testing a class is no different than simple functions: instantiate the class. But one step at a time. and write an assertion. create an empty directory on your desktop.php 2 3 class Calculator { 4 5 } Next. To finish up this chapter. let’s use TDD to write the obligatory calculator class.php . call the desired method. Rest assured that we’ll dig into lots of fancy stuff over the course of this book. if working along. } } Immediately upon running the test . . create Calculator. Create a new composer.we’ll see: 1 Class 'Calculator' not found Of course. php option when calling phpunit. To keep things simple. $this->assertSame(0. PHPUnit will inform us that the getResult method does not yet exist. } } Tip: When you require strict comparison.php 2 3 4 5 6 7 8 9 class CalculatorTest extends PHPUnit_Framework_TestCase { public function testResultDefaultsToZero() { $calc = new Calculator.Chapter 5: Unit Testing 101 1 { "autoload": { "classmap": [ "Calculator. I’d like it to keep track of the current result at all times. always opt for assertSame over assertEquals.php" ] } 2 3 4 5 6 7 61 } A simple composer install and composer dump should add this file to the autoloader. it should default to 0 upon instantiation. so let’s create it.php" CalculatorTest. . or create a configuration file. How might we represent that as a test? 1 <?php // CalculatorTest. Now. we’ll stick with the former. 1 phpunit --bootstrap="vendor/autoload.php And we’re green! What do we require from this class next? Well. $calc->getResult()). pass the --bootstrap="vendor/autoload. We’ll also want to remove the slime²². $calc->getResult()). When I call this method.php 2 3 4 5 6 7 8 class Calculator { public function getResult() { return 0. though. we’ll fix it soon. $calc->add(5). $this->assertEquals(5.dropboxusercontent. we’ll need to introduce a $result property to keep track of the current total.jpg . } } Why hardcode 0? Have you forgotten already? We’re slimin’ it old school. 1 <?php // CalculatorTest.Chapter 5: Unit Testing 101 1 62 <?php // Calculator. Until then.com/u/774859/Laravel-Decoded-Stuff/slimer. } 10 11 12 13 14 15 16 } To make this second test pass. $this->assertSame(0. } 9 public function testAddsNumbers() { $calc = new Calculator. the value specified should be added to a property that keeps a tally of the current total.php 2 3 4 5 6 7 8 class CalculatorTest extends PHPUnit_Framework_TestCase { public function testResultDefaultsToZero() { $calc = new Calculator. ²²https://dl. Don’t worry. $calc->getResult()). I’d like to test an add method. if following the rules of TDD. What if you pass a non-numeric value to the add method. $calc->add('five'). we’re using doc-blocks to inform PHPUnit that. given the provided code. though. we expect an exception to be thrown. } Above. . Tip: Think of exceptions as your way. 1 // CalculatorTest. “I take exception to this action. } 6 7 8 9 10 public function getResult() { return $this->result. as the developer.” To throw an exception. 5 public function add($num) { $this->result += $num. which will cause this test to fail.php 2 3 4 5 6 7 8 9 10 /** * @expectedException InvalidArgumentException */ public function testRequiresNumericValue() { $calc = new Calculator. Without altering any production code. I’m seeing a potential snag. of saying. of course.Chapter 5: Unit Testing 101 1 63 // Calculator. } 11 12 13 14 15 } Already. no exception will be thrown. we can’t write any production code until a failing test is written. such as an array or string? In those situations. I want to ensure that an exception is thrown.php 2 3 4 class Calculator { protected $result = 0. $calc->add(1. 7 $this->result += $num.php 2 3 4 5 6 public function add($num) { if ( ! is_numeric($num)) throw new InvalidArgumentException. 2. jokes. watch the test fail.Chapter 5: Unit Testing 101 1 2 64 1) CalculatorTest::testRequiresNumericValue Failed asserting that exception of type "InvalidArgumentException" is thrown. Dre is at the door'. if you hadn’t realized! Even better. 5). now? Write a test that defines what you wish you could do. . $calc->getResult()). 1 // Calculator. such as add(2. Let’s fix that. 1 2 3 4 5 // CalculatorTest. it would be helpful if I could pass a variable number of arguments to the add method. jokes 7 8 9 10 11 12 } Is this process starting to make sense. // jokes. 4). and then write the necessary production code to make it pass. $calc->getResult() ). I’m trying to drill this into your head. 2. $this->assertNotEquals( 'Snoop Doggy Dogg and Dr.that was easy! Next. Here’s the modified code (or at least a first draft) to allow for passing multiple numbers to add. it’s fun (at least for us). 6 $this->assertEquals(10. 3. 8 9 } Excellent .php public function testAcceptsMultipleArgs() { $calc = new Calculator. . } 17 18 19 20 21 } This time. but I’m beginning to worry a bit. $calc->subtract(4). 13 } 14 } 15 16 public function getResult() { return $this->result. What about when we implement the subtract. breaking the don’t repeat yourself (DRY) pattern. let’s add the necessary functionality to subtract numbers. $calc->getResult()). This will return the tests to green. 7 $this->assertEquals(-4. and divide methods? This may result in a lot of repeated code. 6 7 8 9 10 11 12 $this->result += $num.php 2 3 4 class Calculator { protected $result = 0. we filter through all of the passed arguments. the test: 1 // CalculatorTest. For example. and. we add it to the $result property.Chapter 5: Unit Testing 101 1 65 <?php // Calculator. First. multiply.php 2 3 4 5 6 public function testSubtract() { $calc = new Calculator. 8 9 } And then the production code to make it pass. as long as the value is numeric. 5 public function add() { foreach (func_get_args() as $num) { if ( ! is_numeric($num)) throw new InvalidArgumentException. which will fire before each test runs. Refactoring the Tests Did you notice how. 10 } 11 12 } Tip: When you find yourself copying and pasting code from one method to another. and DRY up both the tests and production code. this is a tell-tale sign that refactoring is required. the Calculator was instantiated? Things like this are better extracted to the setUp method. 9 $this->result -= $num. Let’s take a break. } At this point. while the tests are green.php 2 3 4 5 6 7 8 public function subtract() { foreach(func_get_args() as $num) { if (! is_numeric($num)) throw new InvalidArgumentException. for each test.Chapter 5: Unit Testing 101 1 66 // Calculator. 1 2 3 4 public function setUp() { $this->calc = new Calculator. our test class should look like: . } 22 23 24 25 public function testAcceptsMultipleArgs() { $this->calc->add(1. $this->calc->getResult()). $this->calc->getResult()). 2. } 9 10 11 12 13 public function testResultDefaultsToZero() { $this->assertSame(0. // jokes. $this->calc->getResult()). 26 $this->assertEquals(10. 37 $this->assertEquals(5. $this->assertNotEquals( 'Snoop Doggy Dogg and Dr. 3. 4). $this->calc->getResult() ).Chapter 5: Unit Testing 101 1 <?php // CalculatorTest. 38 39 } 40 41 42 public function testSubtractsNumbers() { 67 .php 2 3 class CalculatorTest extends PHPUnit_Framework_TestCase { 4 5 6 7 8 public function setUp() { $this->calc = new Calculator. jokes 27 28 29 30 31 32 } 33 34 35 36 public function testAddsNumbers() { $this->calc->add(5). jokes. Dre is at the door'. } 14 15 16 17 18 19 20 21 /** * @expectedException InvalidArgumentException */ public function testRequiresNumericValue() { $this->calc->add('five'). 6 7 8 9 10 public function getResult() { return $this->result. $this->calc->getResult()). 45 } 46 47 48 } Refactoring the Production Code Now. and performing the arithmetic. } 21 22 23 24 25 26 27 28 29 protected function calculateAll(array $nums. } } . } 11 12 13 14 15 public function add() { $this->calculateAll(func_get_args(). } 16 17 18 19 20 public function subtract() { $this->calculateAll(func_get_args(). 43 44 $this->assertEquals(-4. we’ll extract this logic away to a couple of dedicated methods: calculateAll and calculate. '+'). $symbol) { foreach ($nums as $num) { $this->calculate($num. $symbol). '-'). on to the production code.Chapter 5: Unit Testing 101 68 $this->calc->subtract(4). determining whether they are numeric. Rather than filtering through all arguments. 1 <?php // Calculator.php 2 3 class Calculator { 4 5 protected $result = 0. as a first step. it lays the structure for further complexity in the class. have a celebratory Fig Newton and pat yourself on the back. The problem with the current setup is that every time we want to add a new operation type (add. . It looks like we’ll need to design our calculator a bit differently. Create an interface (our contract). that contains a single method. without having to be concerned over how that operation is performed. break. Just about any time that you use a switch statement. This is specifically why it’s important to think before you begin coding. this class could become unwieldy. This is where polymorphism comes into play. Polymorphism If that switch statement doesn’t feel right to you. Now. there are exceptions to every rule. the calculate method handles all logic for performing the arithmetic. 35 36 37 38 39 40 case '-': $this->result -= $num. break. multiply). Operation. subtract. we must add another method to the class and update the switch statement. 41 42 43 } 44 } 45 46 47 } Though the code above does introduce a couple more methods. $symbol) { if ( ! is_numeric($num)) throw new InvalidArgumentException. Of course.Chapter 5: Unit Testing 101 69 protected function calculate($num. Too many conditionals are a cause for concern. 30 31 32 33 34 switch ($symbol) { case '+': $this->result += $num. Let’s redesign. Very quickly. It would be better if the class could call a method that performs the arithmetic. but use this as a guideline. your class is secretly screaming for polymorphism. Here’s one for addition: 1 2 3 4 5 6 class Addition implements Operation { public function run($num. we next need to update the tests to reflect this design change. By requesting an implementation of the Operation class.Chapter 5: Unit Testing 101 1 2 3 4 5 6 7 8 9 10 70 interface Operation { /** * Perform the arithmetic * * @param integer $num * @param integer $current * @return integer */ public function run($num. $result). That’s the idea! Now that we have one implementation of the Operation interface. 1 2 3 4 5 public function testAddsNumbers() { $this->calc->setOperands(5). } } Notice how simple that is to read? The best part is that it’s a cinch to test. We need to be sure that this run() method is available. we’d create a new class for each type of operation. Next. $result = $this->calc->calculate(). $this->calc->setOperation(new Addition). $current). } The reason why coding to an interface is good practice is because the Calculator class will be calling methods on injected objects. we can have complete assurance that such a method will be available at run-time. also. 6 $this->assertEquals(5. like so: . 7 8 } To make this test pass. $current) { return $current + $num. the production code. needs to be adjusted. 34 } 35 36 37 } Admittedly. } 14 15 16 17 18 public function setOperation(Operation $operation) { $this->operation = $operation. 24 25 26 27 28 29 30 $this->result = $this->operation->run($num. protected $operation. this is slightly more complex. but it’ll pay off in spades when it comes time to extend the functionality. . } 9 10 11 12 13 public function setOperands() { $this->operands = func_get_args(). } 19 20 21 22 23 public function calculate() { foreach ($this->operands as $num) { if ( ! is_numeric($num)) throw new InvalidArgumentException. protected $operands = []. 5 6 7 8 public function getResult() { return $this->result. 31 } 32 33 return $this->result.Chapter 5: Unit Testing 101 1 71 <?php 2 3 class Calculator { 4 protected $result = null. $this->result). Right? 1 2 3 4 5 6 class Multiplication implements Operation { public function run($num. 5). An entire chapter of the book is dedicated to it. Mocks If you’ve been watching closely. $result). and the tests pass. as noted earlier in this chapter). My personal recommendation is to push for as much isolation as possible. We’ve been somewhat lax in this chapter. 3. $result = $this->calc->calculate(). Simply create a new Multiplication class that implements the interface that we created earlier. called Mockery. we’re cheating when the calculate method calls those other classes. when we have a failing test. and pass it through. Calculator will then call its method without knowing how it behaves. $current) { // If this is the first calculation. if you want a quick demonstration for how you might test this class in true isolation. It’s important to realize that there are two schools of thought on this issue (often labeled Chicago vs. London TDD. 8 } 9 10 } That’s it! A simple bit of logic. This is the beauty of polymorphism. Please note that this code leverages a popular PHP mocking framework. 6 $this->assertEquals(30. only because I’m trying to ease you into the process of testing. see below. would we work on the production code. so you’ll come to know it well! . Having said that. 7 8 } Only then. We haven’t yet gotten to stubs and mocks. $this->calc->setOperation(new Multiplication). you might have noticed that we broke one of our rules. Of course. 1 2 3 4 5 public function testMultipliesNumbers() { $this->calc->setOperands(2. we start with a test to verify when our new multiplication feature is finished.Chapter 5: Unit Testing 101 72 Extensibility To multiply numbers as well. the Calculator doesn’t even need to be touched. 7 return $current * $num. // then return the only operand if (is_null($current)) return $num. If unit testing requires isolation. 5 6 7 8 9 $this->assertEquals(5. 24 25 } Remember: this methodology is only effective if the class that has been mocked. 17 18 19 20 // And then it's business per usual! $result = $this->calc->calculate(). 0) ->andReturn(5). 21 22 23 $this->assertEquals(5. $result). too.Chapter 5: Unit Testing 101 1 2 3 4 5 6 73 public function testAddsNumbers() { // Mock all outside objects. $mock = Mockery::mock('Addition'). has tests! If we can prove that the Addition class works perfectly. $sum = $addition->run(5. // They should have their own tests. 7 // All we care about is verifying that // the proper method was called. $mock->shouldReceive('run') ->once() ->with(5. // We're not interested in testing those. 10 } 11 12 13 } . 8 9 10 11 12 13 14 $this->calc->setOperands(5). $sum). 1 <?php 2 3 class AdditionTest extends PHPUnit_Framework_TestCase { 4 public function testFindsTheSumOfNumbers() { $addition = new Addition. we // pass in the mock object $this->calc->setOperation($mock). 0). then there’s absolutely no reason to test it twice. 15 16 // Rather than new Addition. we’ve only just begun. a Subtract class is responsible for subtracting. polymorphism. there’s much more to learn. another way of thinking about this is. Memorize this: The smaller the class. because we used mocks. When mocking the operation class. but it also makes the process of testing significantly easier. Each of those implementations will have its own tests. including unit testing.png . and a touch of Mockery. subtraction.Chapter 5: Unit Testing 101 74 Or. and reduced. Not only does this approach allow for cleaner and more extensible code. the tests below can be improved. you were able to learn a variety of new techniques. refactored.com/u/774859/Laravel-Decoded-Stuff/happy. ²³https://dl. Don’t forget: once you learn more about mocks and stubs. As Happy²³ would say. That’s what we want. we have a well-tested class for performing basic arithmetic. I want you to break each class down to its core responsibility.dropboxusercontent. Hopefully. etc. So what was this all for? Why dedicate so much of a unit testing chapter to understanding polymorphism? When building testable applications. Refer to the Mocks section earlier in this chapter for a teaser. the Calculator class will still return green. but I’ll leave that part in your hands as home-work. if the Addition class does happen to break at some point. Final Source First. we no longer need tests in CalculatorTest for verifying addition. the easier it is to test. Project Complete With just a bit of effort. the CalculatorTest. An Addition class is responsibile for adding numbers. even though we used a generic calculator as the example. the single responsibility principle. You’d certainly want to add more to this. multiplication. Tip: An added bonus to using mocks in situations such as this is that they allow you to drastically reduce the number of tests. But. 4).php 2 3 class CalculatorTest extends PHPUnit_Framework_TestCase { 4 5 6 7 8 public function setUp() { $this->calc = new Calculator. 39 40 41 42 $this->assertEquals(10. 20 $this->assertEquals(5. $this->assertNotEquals( 'Snoop Doggy Dogg and Dr. 3. 2. Dre is at the door'. $result = $this->calc->calculate(). $result = $this->calc->calculate(). $result). $this->calc->setOperation(new Addition). $this->calc->calculate(). 21 22 } 23 24 25 26 27 28 29 30 31 32 /** * @expectedException InvalidArgumentException */ public function testRequiresNumericValue() { $this->calc->setOperands('five'). 75 . } 9 10 11 12 13 public function testResultDefaultsToNull() { $this->assertNull($this->calc->getResult()). $result). } 33 34 35 36 37 38 public function testAcceptsMultipleArgs() { $this->calc->setOperands(1. } 14 15 16 17 18 19 public function testAddsNumbers() { $this->calc->setOperands(5).Chapter 5: Unit Testing 101 1 <?php // CalculatorTest. $this->calc->setOperation(new Addition). $this->calc->setOperation(new Addition). 1 <?php // Calculator. $this->calc->setOperation(new Subtraction). $this->calc->setOperation(new Multiplication). 5). is when you have a class that is only applicable within the context of another. $result = $this->calc->calculate(). $result = $this->calc->calculate(). $current) { return $current + $num. Please note that the various classes have been grouped for convenience. itself. } 6 7 8 9 10 11 class Addition implements Operation { public function run($num. $current). This can be applied to exception classes and value objects. each class should be contained within its own file. in this author’s opinion. $result). $result). The only exception to this rule. jokes.Chapter 5: Unit Testing 101 76 $result ). 55 } 56 57 public function testMultipliesNumbers() { $this->calc->setOperands(2. jokes 43 44 } 45 46 // Notice how these next two tests are redundant. In your projects. // jokes. 58 59 60 61 62 63 $this->assertEquals(30. They're unnecessary. 47 48 49 50 51 52 53 54 $this->assertEquals(-4. } . public function testSubtractsNumbers() { $this->calc->setOperands(4).php 2 3 4 5 interface Operation { public function run($num. 64 } 65 66 67 } And the Calculator class. 3. now that // we're using polymorphism. $num. protected $operands = []. } 25 26 27 28 29 } 30 31 class Calculator { 32 33 34 35 protected $result = null.Chapter 5: Unit Testing 101 12 } 13 14 15 16 17 class Multiplication implements Operation { public function run($num. $current) { if (is_null($current)) return $num. } 46 47 48 49 50 public function setOperation(Operation $operation) { $this->operation = $operation. } 51 52 53 public function calculate() { 77 . 36 37 38 39 40 public function getResult() { return $this->result. $current) { return $current . } 41 42 43 44 45 public function setOperands() { $this->operands = func_get_args(). 19 } 20 21 } 22 23 class Subtraction implements Operation { 24 public function run($num. 18 return $current * $num. protected $operation. 62 } 63 64 65 } Summary Plenty of unit testing introductions will overwhelm you with theory and jargon.it’s simply not the right way to go about things. in time you’ll take another. 54 55 56 57 58 $this->result = $this->operation->run($num. Contact. folks. This subject matter is just too dry not to sporadically throw in some geeky movie references. I’m reminded of a quote from the movie.Chapter 5: Unit Testing 101 78 foreach ($this->operands as $num) { if ( ! is_numeric($num)) throw new InvalidArgumentException. But who does this serve? It’s not dissimilar to teaching basic Math by beginning with Geometry . . 59 } 60 61 return $this->result.” Yes. “This was just the first step. $this->result). the movie quotes will continue. this rule is as much a product of time constraints as it is common sense. visit the issues²⁵ section of the repository. it’s best to first create a Proposal that describes your intentions. and create a new thread with a descriptive title that begins with [Proposal].Chapter 6: Contributing to Laravel Using TDD Would you prefer a visual walk-through²⁴ of this chapter? There’s a basic rule in the open source community: if your pull request does not include tests. and does not break existing functionality. Particularly for larger projects.com/laravel/framework/issues . If you don’t. the pull requests inevitably follow.to/laravel-6 ²⁵https://github. only to find that Taylor doesn’t quite feel that it belongs in core. if we intend to use test-driven development in our own projects. Creating a Proposal Before contributing to Laravel. A full understanding is vital for today’s modern programmer. Warning: Please note that this chapter assumes that you have a basic understanding of Git. Besides. What exactly does your code do? The tests will provide an example. it goes without saying that. which is fantastic! However. then we should also follow this same rule when contributing to open source projects. A pull queue alone can often feel like a full time job! Offering companion tests for your contributions can help in a number of ways: 1. then don’t bother. A test offers immediate proof that your code works correctly. To submit a proposal. 2. More than anything. this is meant to protect you from potentially wasting hours writing new code. now is the best time to learn. Once a project reaches a certain level of popularity. Here’s an example: ²⁴http://enva. Tools like Travis CI (covered later in this book) make this even more helpful. it’s important to remember that most of the lead developers behind these projects are managing them in their spare time. like Laravel. even if they ultimately apply their own stamp to the finished implementation. In response. GitHub was designed specifically for managing projects. along with the name of the file that should be corrected. If he responds favorably. For simple bug fixes. The important thing. This is unsustainable and will only lead to an ignored email. is that you’re helping.Chapter 6: Contributing to Laravel Using TDD 1 80 [Proposal] Add selectMonth and selectYear to FormBuilder Create a proposal before submitting a pull request. Taylor will either give you a thumbs up or down. Ultimately. Your Local Copy Contributing to Laravel is not a matter of finding a bug and emailing or tweeting Taylor with a snippet of code. it’s vital that your contribution feels right to them. however. so use it! To contribute to Laravel. though. you may omit the proposal. As such. it’s their baby. then you’re free to get to work! Tip: Never feel badly if the owner of a project rejects or modifies your pull request. . the first step is to clone the core framework. while leaving the master branch untouched. Most development teams will have their own naming conventions for branches. it’s a good idea to create a new Git branch for each bug fix or feature. you should have both a tests and src directory.com/laravel/framework. This allows you to spend as much time needed. Next.git Tip: The standard installation of the Laravel framework won’t include each package’s PHPUnit tests. that still holds true here. . Remember when I noted that it’s a best practice for tests to mimic the folder structure of the files in which they’re associated? Well. Branching As a basic rule of thumb. Folder structure for the Laravel framework. install the repository’s dependencies: 1 composer install --dev At this point. Here are two examples: 1 2 git checkout -b feature/bookmarking git checkout -b bug/messages-posted-twice This single command will both create a new branch and check it out. Always clone laravel/framework. as always.Chapter 6: Contributing to Laravel Using TDD 1 81 git clone https://github. Here are a few Laravel-specific examples: 1 2 // WRONG foreach($names as $name) { 3 4 } 5 6 7 8 // CORRECT foreach ($names as $name) { 9 10 } All functions and control structure braces should be on their own line. The only point when this is not true is for the opening class brace. Always match the project owner’s style. . 1 2 3 // WRONG if (!$this->isOld()) { 4 5 } 6 7 8 9 // CORRECT if ( ! $this->isOld()) { 10 11 } Notice that a space surrounds the not operator.Chapter 6: Contributing to Laravel Using TDD 82 Coding Guidelines An important component to successful pull requests is adherence to the project’s coding guidelines. Also. take note of the space after foreach. of course! . but you’ll learn these in due time. we’d start with a test. in a perfect world. however. the former syntax is preferred. not your own. How might we implement that sort of functionality? Well.4 specific must be changed. Agenda Wouldn’t it be helpful if Laravel’s FormBuilder class offered a couple of additional methods for selectYear and selectMonth? Rather than dynamically creating a <select> element and all of the applicable <option>s.3. 1 2 // WRONG return ['foo' => 'bar']. and it’ll take care of the rest. 3 4 5 // CORRECT return array('foo' => 'bar'). So. 5 6 7 class Foo { 8 9 } 10 11 12 // CORRECT <?php namespace Illuminate\Html. show the same respect for projects on GitHub. Consequently. Hands On Assuming that each of the prior steps have been followed. You’re applying code to his project.Chapter 6: Contributing to Laravel Using TDD 1 2 83 // WRONG <?php 3 4 namespace Illuminate\Html. 13 14 class Foo { 15 16 } Namespace declarations should be on the same line as <?php. let’s use TDD to add a bit of functionality. There are other Taylor-specific guidelines. Clearly. in the same way that you might take off your shoes when entering someone else’s home. Laravel still supports PHP 5. we could instead call a single method. unfortunately. any code that is 5. The most important thing is that you pay attention. we’ll take a couple of short-cuts here and there. 6 7 8 9 10 11 12 $this->assertEquals( '<select name="year"><option value="2000">2000</option><option value="200\ 1">2001</option></select>'. As should be familiar to you by now. let’s add a new test to describe the end goal. when running the tests… 1 phpunit tests/Html …we’ll be informed that the selectYear method does not exist.php 2 3 4 5 public function testFormSelectYear() { $select = $this->formBuilder->selectYear('year'. $select ). 2. the name of the game is tiny steps. 3. However. Rinse and repeat. Of course. Run the tests. 1 // tests/Html/FormBuilderTest. we should only have written enough of the test to make it fail. 2001). Watch it fail. Within this file.php. or change the message. } Pretty simple. we expect the provided HTML fragment in return. The FormBuilder class is located in src/Illuminate/Html/FormBuilder.Chapter 6: Contributing to Laravel Using TDD 84 selectYear The tests for the FormBuilder class are located within tests/Html/FormBuilderTest. 1.php. this would be Form::selectYear()). this doesn’t translate overly well to a book. Below the select method. As such. and both a name and range are passed as arguments. Tip: Technically. if following the rules of TDD. 2000. right? When the selectYear method is called (using its respective facade. add the new one: . Write just enough code to make the test pass. Chapter 6: Contributing to Laravel Using TDD 1 // src/Illuminate/Html/FormBuilder. $end). $selected. $options). At this point. } Ahh. 5 // We want the value for each option to // be the same as the text content $range = array_combine($range. 10 11 } 85 . $options = arra\ y()) { $range = range($begin. we can begin implementing the necessary production code: 1 2 3 4 public function selectYear($name. $range). $begin. 6 7 8 9 return $this->select($name. now we’re on to a better failing message: 1 2 3 1) FormBuilderTest::testFormSelectYear Failed asserting that an array contains '<select name="year"><option value="2000"\ >2000</option><option value="2001">2001</option></select>'. iterator or string Hmmm…let’s change the message by sliming it.php 2 3 4 public function selectYear() { 5 6 } Time to rinse and repeat! Now. PHPUnit informs us: 1 2 3 1) FormBuilderTest::testFormSelectYear PHPUnit_Framework_Exception: Argument #2 of PHPUnit_Framework_Assert::assertConta\ ins() must be a array. 1 2 3 4 public function selectYear() { return array(). $selected = null. $range. $end. just to verify that applying a selected value and options work as expected. Here’s the new implementation: 1 2 3 4 5 6 7 public function selectYear() { return call_user_func_array( array($this. 'selectRange').Chapter 6: Contributing to Laravel Using TDD 86 It passes! Because the FormBuilder class already offers a select method. . It could be applied to any range. selectRange. there’s no need to reinvent the wheel. 13 // We want the value for each option to // be the same as the text content $range = array_combine($range. $selected. we’re free to refactor. Because we have passing tests. we’d see red! Luckily. $options = arr\ ay()) { $range = range($begin. However. perhaps it would be better to extract this code out to its own method. and then simply call it from selectYear. $selected = null. $options). 18 19 } We’ll know if our refactoring was successful. With that in mind. $range). We should add a couple more tests . $begin. we don’t. $end). $range. We only need to create the applicable array. } 8 9 10 11 12 public function selectRange($name. $end. notice that this code isn’t specific to years. 14 15 16 17 return $this->select($name. and then pass it to select(). because. func_get_args() ). if it was wasn’t. 2001. $this->assertEquals('<select id="foo" name="year"><option value="2000">2000</\ option><option value="2001">2001</option></select>'. 2000. I’d be more apt to split these into three unique methods. In my own applications. $this->assertEquals('<select name="year"><option value="2000" selected="selec\ ted">2000</option><option value="2001">2001</option></select>'.Chapter 6: Contributing to Laravel Using TDD 1 2 3 4 5 6 87 public function testFormSelectYear() { $select1 = $this->formBuilder->selectYear('year'. $this->assertContains('<select name="month"><option value="1" selected="selec\ ted">January</option>'. null. null. array('id' => 'foo')\ ). $month2). $select2). $month1). 2000. } And the production code to make them pass: . 2001. $month3). $select3 = $this->formBuilder->selectYear('year'. 2000. array('id\ ' => 'foo')). 7 8 9 10 11 12 13 14 $this->assertEquals('<select name="year"><option value="2000">2000</option><o\ ption value="2001">2001</option></select>'. '1'). adding selectMonth shouldn’t be too difficult. $month2 = $this->formBuilder->selectMonth('month'. $select3). $month3 = $this->formBuilder->selectMonth('month'. We begin with a few tests (we’ll move more quickly this time): 1 2 3 4 5 6 public function testFormSelectMonth() { $month1 = $this->formBuilder->selectMonth('month'). $select2 = $this->formBuilder->selectYear('year'. $select1). } Note that we’re grouping these three assertions into a single method primarily because we’re following the basic format of the FormBuilderTest class. so the functionality must have been implemented correctly! selectMonth Now that both selectYear and selectRange have been implemented. The tests return green. 7 8 9 10 11 12 13 14 $this->assertContains('<select name="month"><option value="1">January</option\ >'. '2000'). $this->assertContains('<select id="foo" name="month"><option value="1">Januar\ y</option>'. 2001). ” Tip: The strftime function is particularly helpful.com/laravel/framework/commit/308b71034a3543b447e27c12bb613ad1f5621620 . $options). the month’s value can reflect the developer’s preferred spoken language. Summary Newcomers to GitHub often feel hurt upon having their pull requests rejected. mktime(0. or shouldn’t continue contributing. not yours. Guess what? It’s time to write your first proposal! ²⁶https://github. $month)). } 8 return $this->select($name. 0. $selected. “it couldn’t be simpler. foreach (range(1. 12) as $month) { $months[$month] = strftime('%B'. $options = array()) { $months = array(). Just because your implementation or feature doesn’t mesh well with the project’s creator doesn’t mean that you’re a bad coder. $months. $selected = null.Chapter 6: Contributing to Laravel Using TDD 1 2 3 4 5 6 7 88 public function selectMonth($name. It’s their baby. we can magically fast-forward in time view the merged pull request that we prepared in this chapter²⁶. Great job. 0. It simply means that the creator had a different vision for that particular piece of functionality. 9 10 } As Doc would say. as it’s locale aware. Using the magic of eBook publishing. Thick skin is the key. . yes.beyond “set this property on the object” . What to Test I’ve drilled this into your head multiple times now. plan on. In Laravel. skinny controller” before. such as getters and setters. I often compare controllers to the cops who direct traffic. testing: • • • • • Validations Scopes Accessors and Mutators Associations/Relationships Custom Methods Accessors and Mutators Though opinions vary. More directly. load views. send messages to the domain. at the very least.Chapter 7: Testing Models You’ve surely heard the phrase. “fat model.because that’s what they do: direct traffic. when the power goes out . it’s crucial that your models be fully tested. Here are a couple of examples. do prepare tests to verify that it works as expected. this is simply a pattern which dictates that it’s better to keep your controllers as minimal as possible: handle responses. However. For this reason. The basic rule is that everything that has an opportunity to break should be tested.then. If not. my recommendation is to omit tests for basic things. to transform a model’s attributes when getting or setting. we use the following syntax (using age as the name of the attribute): 1 2 // Accessor public function getAgeAttribute($age) {} 3 4 5 // Mutator public function setAgeAttribute($age) {} This can be particularly helpful when a value needs to be manipulated in some way. Calculations or operations of any real complexity should be placed within a model or service. there’s one exception to this rule: if any logic is performed . for some ridiculous reason. you’d be right! 1 2 3 4 public function testAutomaticallyCompensatesForCatYears() { $cat = new Cat.Chapter 7: Testing Models 90 Cat Years Example What if. // 42 If you’re thinking that this would be easy to test. // true 6 7 } Password Hashing Example As another example. } } Now. 5 $this->assertEquals(42. when age is set on the model. and wanted to automatically translate a provided age (in human years) into cat years before saving to the database? This is a perfect use case for mutators. you had a Cat model. 1 <?php 2 3 4 5 6 7 8 class Cat extends Eloquent { public function setAgeAttribute($age) { $this->attributes['age'] = $age * 7. In effect: 1 2 $cat = new Cat. a mutator will handle this task quite nicely. which multiplies the age by seven (the ratio of cat to human years). 3 4 echo $cat->age. $cat->age). Well. let’s say that we want to remove the need to manually hash a user’s password within the controller. it will first be filtered through this method. $cat->age = 6. once again. . $cat->age = 6. Because the mutator calls Hash::make. if we want to test in isolation. 8 9 } This test uses a mock as a way of saying: “I expect the make method on the Hash class to be triggered one time. This technique offers three advantages: 1. and we’ll be notified immediately upon running phpunit. Approach these in the same way that you would any other test. because its return value will be somewhat random. then that class needs to be mocked. We continue testing in complete isolation from external objects. 4 $author = new User. $author->password = 'foo'. rather than calling the original method. Luckily. } } Testing this method is only slightly more tricky. $author->password). 2. Testing Hash::make can be tricky. If the mutator does not call Hash::make. . the expectation will fail. because it’s a facade. For example. 3. there will also be various custom methods within your model that should be tested. hashed. Though you could. 1 2 3 public function testHashesPasswordWhenSet() { Hash::shouldReceive('make')->once()->andReturn('hashed'). why bother when you can instead mock the dependency entirely? Custom Methods Of course. perhaps. let’s write the test first. let’s assume that an Article model should offer a way to retrieve meta information in readable form. ensure that the length of the mutated password is sixty characters (matching a hash’s length).Chapter 7: Testing Models 1 91 <?php 2 3 4 5 6 7 8 class User extends Eloquent { public function setPasswordAttribute($password) { $this->attributes['password'] = Hash::make($password). 5 6 7 $this->assertEquals('hashed'. When this occurs. doing so is trivial. This time. I’ll just return the string. let’s stick with PHP’s native sprintf function.'. } } Testing is easy! Simple Query Methods A question that I’m often asked relates to how you would go about testing basic query methods.'. 10 11 12 13 } 14 15 } So. For example. $this->author ). like so: .Chapter 7: Testing Models 1 92 <?php 2 3 4 5 6 7 8 class ArticleTest extends TestCase { public function testGetsReadableMetaData() { $article = new Article. to be returned. 9 $this->assertEquals( '"My First Article" was written by Perd Hapley. 1 <?php 2 3 4 5 6 7 8 9 10 11 12 class Article extends Eloquent { public function meta() { return sprintf( '"%s" was written by %s. The production code to make this test pass simply needs to fetch a couple attribute values and format them as a sentence. you might wrap this query in a readable method. when a meta method is called for the given model. $article->author = 'Perd Hapley'. imagine that you need to fetch the oldest user from the Users table. $article->meta() ). $this->title. $article->title = 'My First Article'. we expect the string. With Eloquent. “My First Article” was written by Perd Hapley. Rather than dealing with a bunch of concatenation. Verify that the correct record was returned Here’s an example. 2. opinions vary²⁷. Factory::create('User'. using the Laravel Test Helpers package²⁸ (you’ll learn more about this soon). Why? Well that would be the only thing that the test does. and verify that the correct row is returned. Because this entire method is simply a wrapper for a query builder. Insert a couple test rows into the DB. The best choice is to: 1. As with many things. 7 8 9 // Assert $this->assertEquals(30.com/JeffreyWay/5674014 ²⁸https://github. 10 11 12 } ²⁷https://gist. my recommendation is to write an integration test (which will require a test database). Don’t bother testing it at all.Chapter 7: Testing Models 1 93 <?php 2 3 4 5 6 7 8 class User extends Eloquent { public function getOldest() { return $this->orderBy('age'. it doesn’t make too much sense to partially mock the orderBy method. ['age' => 30]). Write an integration test. Partially mock the User class. 3. and verify that the orderBy method is called.github. call the method. $oldest->age). that does this very thing: 1 2 3 4 5 public function testGetsOldestUser() { // Arrange: Insert two test rows into a test DB Factory::create('User'. however.com/JeffreyWay/Laravel-Test-Helpers . Is that helpful? Not really. ['age' => 20]). 6 // Act: call the method $oldest = (new User)->getOldest(). Call the method (without mocking) 3. } } How would you test this? Well. Insert a couple of test records into the DB 2. I see three possibilities: 1. 'desc')->first(). Otherwise.php 2 3 4 5 public function testIsInvalidWithoutAName() { $author = new Author. I prefer to create bash aliases. right? How might we verify that in a test? 1 // app/tests/models/AuthorTest. etc. But. 6 $this->assertFalse($author->validate()). to create a new Author model. Because we haven’t yet fully gone over mocks and stubs. Should you run the tests again. we need to ensure that this new validate method works correctly. fail. 1 <?php // app/models/Author.com/JeffreyWay/Laravel-4-Generators . don’t worry if some of the following code appears foreign to you.php 2 3 class Author extends Eloquent {} Tip: When using my generators tool²⁹. As an example. 7 8 } Notice how the name of the test describes our desired behavior for the model. 1 PHP Fatal error: Class 'Author' not found There’s the next step: create the model. Let’s add this to a BaseModel class that all models can inherit from. As such. you’ll be notified that a validate method does not exist on the model. Running phpunit will. before continuing on with this current test. of course. I would simply run g:m author from the Terminal. ²⁹https://github.Chapter 7: Testing Models 94 Validations Another relatively simple way to ease into the process of testing models is to test validations. to create a new author. it’s invalid. a name field or property must be present. such as g:m to generate a model. g:mig for a migration. alerting you that an Author model does not exist. $model::$rules = ['title' => 'required']. $result = $this->model->validate(). $this->assertEquals('messages'. 11 12 } 13 14 public function testReturnsTrueIfValidationPasses() { Validator::shouldReceive('make')->once()->andReturn( Mockery::mock(['passes' => true]) ). 35 36 } 37 38 } Again. notice how the names of the tests describe how the code should behave. Because we’re testing in isolation. 33 34 $this->assertFalse($result).Chapter 7: Testing Models 1 95 <?php // app/tests/BaseModelTest. 15 16 17 18 19 20 $this->model->title = 'Foo Title'. 21 22 23 $this->assertTrue($result). $this->model->errors). we don’t need proof that that class works .php 2 class BaseModelTest extends TestCase { 3 4 protected $model. 7 8 9 10 $this->model = $model = new BaseModel. it’s paramount that we refrain from triggering Laravel’s native Validator class. 5 6 public function setUp() { parent::setUp(). 27 28 29 30 31 32 $result = $this->model->validate(). 24 } 25 26 public function testSetsErrorsOnObjectIfValidationFails() { Validator::shouldReceive('make')->once()->andReturn( Mockery::mock(['passes' => false. Or. in other words. These two tests account for both paths through the method: successful and failed validation. This is preferred over testValidate. 'messages' => 'messages']) ). 7 8 9 10 if ($v->passes()) return true. Now that a validate method is available to all models. static::$rules). 15 } 16 17 } This validate method consists of fairly boilerplate code to validate the current set of attributes against a static rules property that should exist on each model.php 2 3 4 5 class BaseModel extends Eloquent { public $errors. . 7 8 } And its related production code. 1 // app/tests/models/AuthorTest. we can mock it. which now inherits from the newly added BaseModel. 11 12 $this->errors = $v->messages(). 13 14 return false. 6 public function validate() { $v = Validator::make($this->attributes.Chapter 7: Testing Models 96 correctly. Here’s the associated code to make those two tests pass: 1 <?php // app/models/BaseModel. As such.php 2 3 4 5 public function testIsInvalidWithoutAName() { $author = new Author. Here it is again for reference. You’ll learn all of this in the Mockeryspecific chapter shortly. 6 $this->assertFalse($author->validate()). Taylor has already tested it. we can return to the former test. php 2 3 4 5 6 7 8 public function testIsInvalidWithoutAValidEmail() { // Set fixture $author = new Author. 10 11 } Once again. An email address should be required. 1 2 1) AuthorTest::testIsInvalidWithoutAName Failed asserting that true is false. but it should adhere to the format of a real email address.'). too. because no rules have been set. it did not. $author->email = 'foo'. as expected. fail. we’re back to a failing test. 1 $this->assertFalse($author->validate(). 1 <?php // app/models/Author. When making use of the generic assertTrue and assertFalse methods. update the $rules property on the Author model.php 2 3 4 5 6 7 class Author extends BaseModel { public static $rules = [ 'name' => 'required' ].php 2 3 4 5 class Author extends BaseModel { public static $rules = []. always provide a custom message to avoid meaningless failures.Chapter 7: Testing Models 1 97 // app/tests/models/AuthorTest. like Failed asserting that true is false. . } This should return us to green! Let’s try a couple more. as needed. 'Expected validation to fail. } At this point. 'Expected validation to fail. $author->name = 'Joe'. 1 // app/tests/models/AuthorTest. To fix that.'). Not only should an email address be set. Although we expected the validation to fail. PHPUnit should. 9 $this->assertFalse($author->validate(). 1 // app/tests/models/AuthorTest. If you think about it.php 2 3 4 5 6 7 8 class Author extends BaseModel { public static $rules = [ 'name' => 'required'.'). Write a test (which touches the database once) to verify that this never happens. 10 // Now. } Tip: Refer to the Laravel documentation³⁰ for a full list of available validation rules.Chapter 7: Testing Models 1 98 <?php // app/models/Author. $author->email = '[email protected] 2 3 4 5 6 7 8 9 public function testIsInvalidWithoutUniqueEmail() { // Set fixture $author = new Author. We don't want that.laravel. a valid email is not enough. 'email' => 'required|email' ]. try to insert a new author // with the same email. We don’t want multiple authors with the same email address. $author->name = 'Joe'.com'. 11 12 13 14 15 16 // Ensure that validation fails. $author->email = 'joe@example. $author = new Author. 17 18 19 20 } ³⁰http://four. 'Expected validation to fail. $this->assertFalse($author->validate(). $author->save().com'.com/docs/validation#available-validation-rules . because there already is // an author with that email registered. we should also ensure that it’s a unique one. $author->name = 'Frank'. 4 or higher. 1 <?php // app/models/Author.Chapter 7: Testing Models 99 Fixtures: Think of a fixture as a dummy record that can be inserted into a table for the purposes of testing. 'email' => 'required|email|unique:authors' ]. Laravel offers a unique:TABLENAME validation rule that will serve our needs nicely here. } That should do it. as you add additional fields. If working with PHP 5.php 2 3 4 5 6 7 8 class Author extends BaseModel { public static $rules = [ 'name' => 'required'. as you might imagine. 3 assertions) Warning: One glaring problem with this approach is that. 1 OK (3 tests. More on that later in this chapter. such as assertValid and assertNotValid. it makes sense to abstract this away to a custom assertion. as needed. it can lead to false positives in your tests. . and then update individual fields. testing validations is so common. Helpers Because. traits are a helpful choice for storing these sorts of mixins. It’s better to begin with a complete model. Back to green. php". "app/tests/helpers" ] } The wonderful thing about traits is that they can easily be imported into existing classes.' ). To pull these two mixins into AuthorTest.. so don’t forget to update composer. 'Did not expect model to pass validation.json 2 3 4 5 6 7 8 9 "autoload": { "classmap": [ // ..Chapter 7: Testing Models 1 100 <?php // app/tests/helpers/ModelHelpers. 'Model did not pass validation. } 11 public function assertNotValid($model) { $this->assertFalse( $model->validate(). and then composer dump-autoload. it’s as easy as doing: . via the use keyword. 1 // composer. "app/tests/TestCase.php 2 3 4 5 6 7 8 9 10 trait ModelHelpers { public function assertValid($model) { $this->assertTrue( $model->validate(). } 12 13 14 15 16 17 18 19 } We’ve introduced a new app/tests/helpers directory.' ). to create an object with all of the necessary fields. $author->email = 'joe@example. there must an easier way. $author->name = 'Joe'. 14 $this->assertNotValid($author). 1 // app/tests/models/AuthorTest.com'. 4 5 } Now. 3 // . Surely. Further. such as: 1 $author = Factory::author(). 7 8 } 9 10 11 12 13 public function testIsInvalidWithoutAValidEmail() { $author = new Author.Chapter 7: Testing Models 1 2 101 class AuthorTest extends TestCase { use ModelHelpers. we’ve resorted to the somewhat cumbersome task of manually building test objects.. $author->name = 'Joe'. we could leverage a factory. and insert it into the DB: . 1 2 3 $author = new Author. Or. which will handle the process of dynamically building the attributes for a model. 15 16 } Factories Up until this point. it should follow the clean Laravel style. this more readable assertion syntax may be used for testing validations. Ideally..php 2 3 4 5 public function testIsInvalidWithoutAName() { $author = new Author. 6 $this->assertNotValid($author). com']).com']). ['email' => 'foo@foo. 19 20 } 21 22 23 24 public function testIsInvalidWithoutUniqueEmail() { $author = Factory::create('author'. 1 Factory::create('author'. ['email' => 'joe@example. now. $this->assertNotValid($author). Finally. 7 8 9 10 public function testIsInvalidWithoutAName() { $author = Factory::author(['name' => null]). I’ve already done the work for you! It’s available on Packagist³¹. 11 $this->assertNotValid($author). 28 29 30 // Let's make sure that it fails.php 2 3 use Way\Tests\Factory.json file as detailed on the Packagist page. $author = Factory::author(['email' => 'joe@example. 12 13 } 14 15 16 17 public function testIsInvalidWithoutAValidEmail() { $author = Factory::author(['email' => 'foo']).com']). Though building a tool like this from scratch is beyond the scope of this book. the tests may be updated to: 1 <?php // app/tests/models/AuthorTest. we also need to have the option of overriding one or more fields’ default value. 18 $this->assertNotValid($author). ³¹https://packagist.org/packages/way/laravel-test-helpers . pull in the dependencies. 4 5 6 class AuthorTest extends TestCase { use ModelHelpers. Update your composer.Chapter 7: Testing Models 1 102 Factory::create('author'). 25 26 27 // Now try to insert a new author with the same email. and. the $user variable will be equal to random data that fits the data types for each field.Chapter 7: Testing Models 103 } 31 32 33 } Much cleaner! Laravel Test Helpers The package that you just installed eases the process of writing tests for Laravel applications. by offering: • A Factory utility (quickly create and populate models) • Various Model test helpers (assertValid. Factories Ever found yourself repeatedly creating test models over and over? 1 2 3 $user = new User. $user->email = 'foo@example. 4 5 class UserTest extends TestCase { 6 public function testBasicExample() { $user = Factory::attributesFor('User'). } 7 8 9 10 11 } Now. etc. use a factory! 1 <?php 2 3 use Way\Tests\Factory. We reviewed these in Chapter 4.) • Assert and Should PHPUnit wrappers. assertBelongsTo. This can very quickly flood your test classes.com' $user->name = 'Joe'. Instead. Something like: . This works. then you have two options: 1 $user = Factory::user(). If you want the full Laravel collection. say. you can also use the create method.com" 'age' => int(26) 'created_at' => string(19) "2013-05-01 02:21:49" 'updated_at' => string(19) "2013-05-01 02:21:49" } Overrides There will be times. though. but if it’s not. . This can be particularly helpful for validation. unless an email is provided.array(6) { 'id' => NULL 'name' => string(3) "Kim" 'email' => string(15) "kim@example. if the model is in the global namespace. which will instantiate the model. a model should be invalid. ['email' => null]). and save it to the database. fill it with dummy data. If you happen to be working with a real test database. Models The static attributesFor method is great for fetching an array of attributes. This technique uses callStatic to allow for the readable syntax shown above.Chapter 7: Testing Models 1 2 3 4 5 6 7 8 9 10 11 12 13 14 104 . 1 $user = Factory::make('Models\User'). where. Any fields specified in the second argument will override the random defaults. you’ll want to use the make method. when you need to specify values for some fields. 1 $user = Factory::attributesFor('User'. . In the example above. we are asserting that a User model should be invalid. Simply add use Way\Tests\ModelHelpers. Let’s assert that a User model has many Posts. and you should be good to go. 12 } 13 14 15 } All model test helpers are stored as traits. Asserting Relationships There are also various assertions for Laravel relationships. Test Helpers This package also includes a growing list of test helpers. 8 9 10 11 $this->assertNotValid($user). unless its name field is not empty. Currently. This makes them super easy to import into our test class. assertValid and assertNotValid 1 <?php 2 3 use Way\Tests\Factory.Chapter 7: Testing Models 1 105 $user = Factory::create('User'). the assertion will look for a validate method on the model. 7 public function testIsInvalidWithoutName() { $user = Factory::user(['name' => null]). to the top of the class. 4 5 6 class UserTest extends TestCase { use Way\Tests\ModelHelpers. Go ahead and add a posts method to User. 'posts'. but. . where not a huge amount of logic is being performed. when it comes to models.Chapter 7: Testing Models 1 2 3 4 106 public function testHasManyPosts() { $this->assertHasMany('posts'. 'User'). Currently. } Running phpunit will return: bash 3) UserTest::testHasManyPosts Expected the 'User' class to have method. } And now we’re back to green. it’s vital that you not cut any corners. you can assert: • assertBelongsTo • assertHasOne • assertHasMany Summary Arguments can be made for not testing controllers. Failed asserting that false is true. 1 2 3 4 public function posts() { $this->hasMany('Post'). we’ll dig into what test doubles are. you certainly don’t want to physically touch the file system. and how to integrate Mockery into your testing workflow. called. Mockery³². often. and. In other words. the premier mock object framework for PHP. A stub. Instead. Mocking Decoded A mock object is nothing more than a bit of test jargon that refers to simulating the behavior of real objects. merely ensure that the applicable method on the class was. Stubs: .Chapter 8: Easier Testing With Mockery It’s an unfortunate truth that. the two serve different purposes. is one such tool! In this article. rather than manually read the file to prove that it was updated. In simpler terms. you simply need to ensure that it was. called. is ³²https://github. mock and stub. The various jargon alone can prove overwhelming! Luckily.com/padraic/mockery . This is mocking! There’s nothing more to it than that. while the basic principle behind testing is quite simple. Perhaps an example is in order. thrown about interchangably. This has the potential to drastically decrease the speed of your tests. Mocks vs. on the other hand. in fact. In fact. Imagine that your code triggers a method that will log a bit of data to a file. why they’re useful. you won’t want to execute a particular method. The former refers to the process of defining expectations and ensuring desired behavior. In these situations. and help to make the process as simple as it can be. fully introducing this process into your day-to-day coding workflow is more difficult than you might hope. it’s best to mock your file system class. a mock can potentially lead to a failed test. a variety of tools have your back.a familiarity with mocking will quickly become essential. When testing this logic. when testing. Particularly as your development process matures . You’ll frequently hear the terms.including embracing the single responsibility principle and leveraging dependency injection . simulate the behavior of objects. Never allow an initially confusing piece of terminology to deter you from learning a new skill. in fact. Remember: jargon is just jargon. Mockery is a simple. As a group. more importantly. Among these solutions . Mockery may be installed with Composer. writeability. what’s this Composer³³ thing? It’s the PHP community’s preferred tool for dependency management. ³³http://getcomposer.and most notable of the set . and. PHPUnit. As you’ll find. there are multiple ways to fake data. dependent upon the scenario. ships with its own API for mocking objects.is Mockery. the more likely it is that the developer simply (and sadly) won’t. each of which has its advantages. unfortunately. however. which allow for increased readability. Installation Like most modern PHP tools. Like most PHP tools these days. The most popular testing library for PHP. but powerful utility. a framework-agnostic mock object framework. it’s the industry standard for modern PHP development. a variety of third-party solutions are available through Packagist (Composer’s package repository). . it can prove cumbersome to work with. Wait. Luckily. Designed as a drop-in alternative for those who are overwhelmed by PHPUnit’s mocking verbosity. in fact. the more difficult testing is. they are referred to as test doubles.Chapter 8: Easier Testing With Mockery 108 simply a dummy set of data that can be passed around to meet certain criteria. As you’re surely aware. the recommended method to install Mockery is through Composer (though it’s available through Pear too). It provides an easy way to declare a project’s dependencies.org . As you’ll surely find. ³⁴http://net. a composer install --dev will pull in the package. Perhaps the generator compiles various data. add a new composer. If you’re still manually requiring countless files in each PHP file. The Dilemma Before we can implement a solution. you just might be doing it wrong.Chapter 8: Easier Testing With Mockery 109 and pull them in with a single command. for development. respectively. If working along. and then that data is written to the file system. and another for physically writing the data to a file. your application requires the Mockery library.which dictates that each class should be responsible for exactly one thing . As a modern PHP developer. 1 2 3 4 5 $ composer install --dev Loading composer repositories with package information Installing dependencies (including require-dev) . it’s best to first review the problem. From the command-line. A Generator and File class. Imagine that you need to implement a system for handling the process of generating content and writing it to a file. If following the single responsibility principle .Installing mockery/mockery (dev-master 5a71299) Cloning 5a712994e1e3ee604b0d355d1af342172c6f475f 6 7 8 Writing lock file Generating autoload files As an added bonus. and how to use it. for learning purposes.then it stands to reason that we should split this logic into two classes: one for generating the necessary content.com/tutorials/php/psr-huh/ . or a web service. Refer to Nettuts+³⁴ to learn more. Composer ships with its own autoloader for free! Either specify a classmap of directories and composer dump-autoload.tutsplus. or follow the PSR-0 standard and adjust your directory structure to match. well.json file to an empty project and append: 1 { "require-dev": { "mockery/mockery": "dev-master" } 2 3 4 5 } This bit of JSON specifies that. it’s vital that you have a basic understanding of what Composer is. should do the trick. either from local file stubs. 17 18 19 20 $this->file->put('foo. 21 } 22 23 } Dependency Injection This code leverages what we refer to as dependency injection. such as monkey patching. ask yourself: “How could I test this?” There are techniques. Once again. it’s better to instead wrap such functionality up. as a best practice. but.php 2 3 4 class Generator { protected $file. we wouldn’t be able to mock the File class! Sure.Chapter 8: Easier Testing With Mockery 110 Tip: Why not use file_put_contents directly from the Generator class? Well. which can allow you to overload these sorts of things. 5 public function __construct(File $file) { $this->file = $file. this is simply developer jargon for injecting a class’s dependencies through its constructor method. 1 <?php // src/Generator. we could mock the File class. } 11 12 13 14 15 16 public function fire() { $content = $this->getContent(). . otherwise. rather than hard-coding them. $content). so that it may easily be mocked with tools. like Mockery! Here’s a basic structure (with a healthy dose of pseudo code) for our Generator class. but if its instantiation is hard-coded into the class that we’re testing. Why is this beneficial? Because. } 6 7 8 9 10 protected function getContent() { // simplified for demo return 'foo bar'. there’s no easy way to replace that instance with the mocked version.txt'. rather injecting the class’s dependencies through its constructor method. if you should prefer. Here’s an example: 1 2 3 4 5 6 class Generator { public function __construct(File $file = null) { $this->file = $file ?: new File. that object will be used in the class. On the other hand. while still specifying fallback defaults. } } Now. you can allow for dependency injection. the only difference is that. always inject a class’s dependencies through the constructor. like so: 1 2 3 4 public function setFile(File $file) { $this->file = $file. they’re instead done so through a setter method. doing so is widely considered to be a bad practice.Chapter 8: Easier Testing With Mockery 1 2 3 4 5 111 public function __construct() { // anti-pattern $this->file = new File. } A common criticism of dependency injection is that it introduces additional complexity into an application. Though the complexity argument is debatable in this author’s opinion. if an instance of File is passed through to the constructor. This allows for such variations as: . “How might I test this?” While there are tricks for getting around this hard-coding. all for the sake of making it more testable. Setter injection is more or less identical to constructor injection. Instead. The principle is exactly the same. } The best way to build testable applications is to approach each new method call with the question. if nothing is passed. the Generator will fall back to manually instantiating the applicable class. or via setter injection. first-hand. for the purposes of this tutorial. 1 <?php // tests/GeneratorTest. what the problem is. 8 $generator->fire(). Continuing on. } } Rather simple.php 2 3 4 5 6 7 class GeneratorTest extends PHPUnit_Framework_TestCase { public function testItWorks() { $file = new File. 6 7 8 # Inject a mock of File for testing new Generator($mockedFile). eh? Let’s write a test to see. 3 4 5 # Inject File new Generator(new File). $content).php 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class File { /** * Write data to a given file * * @param string $path * @param string $content * @return mixed */ public function put($path. the File class will be nothing more than a simple wrapper around PHP’s file_put_contents function.Chapter 8: Easier Testing With Mockery 1 2 112 # Class instantiates File new Generator. $generator = new Generator($file). 1 <?php // src/File. $content) { return file_put_contents($path. 9 } 10 11 } . As was noted earlier. right? Well. not exactly. it’s not overly enjoyable to work with.json file optionally accepts an autoload object. though we could make use of PHPUnit native API to serve our mocking requirements. What about when you’ve written dozens more tests? As you can imagine.” Still not convinced? If reduced testing speed won’t sway you.113 Chapter 8: Easier Testing With Mockery Please note that these examples assume that the necessary classes are being autoloaded with Composer. then consider common sense.txt file will be created on the file system. No more messy require statements! If working along. indeed. image “Though the tests pass. why do we have any interest in executing code from the File class? It should have its own tests! Why the heck would we double up? The Solution Hopefully. a foo. that means we can move on to the next task. . work. where you may specify which directories or classes to autoload. the previous section provided the perfect illustration for why mocking is essential. very quickly. Your composer. each time this test is run. While it’s true that the code does. here’s an example for asserting that a mocked object should receive a method. getName and return John Doe. To illustrate this truth. running phpunit will return: 1 OK (1 test. Think about it: we’re testing the Generator class. 0 assertions) It’s green. they’re incorrectly touching the filesystem. your test’s speed of execution will stutter. the File class with Mockery.or simulate the behavior of . Continuing with the example from the previous “Dilemma section.Chapter 8: Easier Testing With Mockery 1 2 3 4 5 6 7 114 public function testNativeMocks() { $mock = $this->getMock('SomeClass'). } 8 9 10 11 public function testItWorks() { $mockedFile = Mockery::mock('File'). within the GeneratorTest class. 1 2 3 4 5 6 7 public function testMockery() { $mock = Mockery::mock('SomeClass'). 'foo bar') ->once(). we can drastically improve its readability. Here’s the updated code: 1 <?php 2 3 4 5 6 7 class GeneratorTest extends PHPUnit_Framework_TestCase { public function tearDown() { Mockery::close(). and returns John Doe PHPUnit’s implementation is confusing and verbose. this time. $mock->shouldReceive('getName') ->once() ->andReturn('John Doe'). . $mock->expects($this->once()) ->method('getName') ->will($this->returnValue('John Doe')).txt'. With Mockery. 12 13 14 15 16 $mockedFile->shouldReceive('put') ->with('foo. } Notice how the latter example reads (and speaks) better.asserting that a getName method is called once. } While it gets the job done . let’s instead mock . we’re asserting that it should call the put method on the File instance. for each item. In this case. they’ll still return green. $generator->fire(). 7 8 } Because we’re using dependency injection. however. and send it the path.where. it’s now a cinch to instead inject the mocked File object.and. you might pass an array to the mock method . If we run the tests again. 17 18 } 19 20 115 } Confused by the Mockery::close() reference within the tearDown method? This static call cleans up the Mockery container used by the current test. and run any verification tasks needed for your expectations. . This may be accomplished. you’ll typically need to specify which methods on this mock object you expect to be called. the key and value correspond to the method name and return value. A class may be mocked using the readable Mockery::mock() method. the File class . consequently. $content). If you only require a simple object. the file system . 6 $this->file->put('foo. and the data.txt'.will never be touched! Again. 1 $generator = new Generator($mockedFile). respectively. foo.Chapter 8: Easier Testing With Mockery $generator = new Generator($mockedFile). 1 // libraries/Generator. there’s no need to touch File. via the shouldReceive(METHOD) and with(ARG) methods. foo bar. Next. perhaps for a user. It should have its own tests! Mocking for the win! Simple Mock Objects Mock objects needn’t always reference a class. along with any applicable arguments.php 2 3 4 5 public function fire() { $content = $this->getContent().txt. when we call $generate->fire(). $user->getFullName(). Continuing on with our Generator/File example. for the purposes of this test’s path. and it should. // Jeffrey Way } Return Values From Mocked Methods There will surely be times. if the file already exists. what if we need to ensure that. in situations such as this. 5 6 7 8 $mockedFile->shouldReceive('put') ->never(). 1 2 $mockedFile->shouldReceive('put') ->never(). an error will be returned: . 4 $mockedFile->shouldReceive('exists') ->once() ->andReturn(true). 9 10 11 $generator = new Generator($mockedFile).Chapter 8: Easier Testing With Mockery 1 2 3 4 5 116 public function testSimpleMocks() { $user = Mockery::mock(['getFullName' => 'Jeffrey Way']). Here’s an updated example: 1 2 3 public function testDoesNotOverwriteFile() { $mockedFile = Mockery::mock('File'). this is easy. thanks to the never() expectation. $generator->fire(). return true. signaling that the file already exists and shouldn’t be overwritten. the put method on the File class is never triggered. it shouldn’t be overwritten? How might we accomplish that? The key is to use the andReturn() method on your mocked object to simulate different states. With Mockery. Should we run the tests again. when a mocked class method needs to return a value. We next ensure that. 12 13 14 } This updated code now asserts that an exists method should be triggered on the mocked File class. so the test expected that $this->file->exists() should be called. for production. though the tests may show green. } 6 7 8 9 10 protected function getContent() { // simplified for demo return 'foo bar'. but the tests are back to green! It’s important to remember that this style of testing is only effective if you do. test the dependencies of your class as well! Otherwise. As a result. } 22 23 24 25 } 26 27 } That’s all there is to it! Not only have we followed a TDD (test-driven development) cycle. $file = 'foo. the code will break. Let’s fix it! 1 <?php 2 3 4 class Generator { protected $file. $content). but that never happened. Our demo this far has only ensured that Generator works as expected. it failed. Aha. Don’t forget to test File as well! . in fact.Chapter 8: Easier Testing With Mockery 1 2 117 Method exists() from File should be called exactly 1 times but called 0 times. } 11 12 13 14 15 16 public function fire() { $content = $this->getContent().txt'. 5 public function __construct(File $file) { $this->file = $file. 17 18 19 20 21 if (! $this->file->exists($file)) { $this->file->put($file. Mockery::any()) ->once(). And as a final (but not limited to) example. maybe the argument needs to match a regular expression. as long as they meet certain criteria. the default is zero or more times (zeroOrMoreTimes()). As demonstrated earlier. this can be particularly helpful when you need to ensure that a particular method is triggered with the necessary arguments. 1 2 3 $mock->shouldReceive('get')->withAnyArgs()->once(). though. its name is a bit misleading. it does not require that the method be triggered.txt should be matched. Here’s a few examples. using the anyOf matcher.txt')->once(). Be careful with this.txt'. $mock->shouldReceive('method')->times(1). let’s allow for an array of acceptable values. . This can be extended even further to allow for the argument values to be dynamic in nature. Perhaps we only wish to ensure that a string is passed to a method: 1 $mock->shouldReceive('get')->with(Mockery::type('string'))->once().Chapter 8: Easier Testing With Mockery 118 Expectations Let’s dig a bit more deeply into Mockery’s expectation declarations. You’re already familiar with shouldReceive. or potentially more times. It’s important to keep in mind that the expectation will only apply if a method is called with these exact arguments. $mock->shouldReceive('put')->with('foo. Let’s assert that any file name that ends with . // the default $mock->shouldReceive('get')->with('foo. When left on its own.txt$/'. $mock->shouldReceive('method')->atLeast()->times(1). There will be times when additional constraints are necessary. a handful of options are available: 1 2 3 $mock->shouldReceive('method')->once(). 'foo bar')->once(). To assert that you require the method to be called once. 1 2 3 $mockedFile->shouldReceive('put') ->with('/\. Or. // do something with $timeout } 9 10 11 12 13 14 } . 'cache. the expectation will only apply if the first argument to the get method is log. rather than the entire object. m::mock(). 1 <?php 2 3 4 5 6 7 class MyClass { public function getOption($option) { return config($option).txt. for the purposes of this example. Let’s imagine. that a method on your class references a custom global function (gasp) to fetch a value from a configuration file. Perhaps we only need it to return a boolean. Tip: Don’t forget. } 8 public function fire() { $timeout = $this->getOption('timeout').. Otherwise.Chapter 8: Easier Testing With Mockery 1 2 3 119 $mockedFile->shouldReceive('get') ->with(Mockery::anyOf('log.txt or cache. you can always alias Mockery as m at the top of your class to make things a bit more succinct: use Mockery as m. This allows for the more succinct.txt')) ->once(). With this code. Partial Mocks You may find that there are situations when you only need to mock a single method. Easy: 1 2 3 $mock->shouldReceive('method') ->once() ->andReturn(false). 1 Mockery\Exception\NoMatchingExpectationException: No matching handler found. we have a variety of options for specifying what the mocked method should do or return.. Lastly. a Mockery exception will be thrown when the tests are run.txt'.. In this case. nonetheless. as we’ve done above. all methods on MyClass will behave as they normally would. Keep in mind that you must always declare the behavior of your mocked methods. which you can think of as setting a default state for the mock object: all methods defer to the main parent class. 1 2 3 4 5 6 public function testPartialMockExample() { $mock = Mockery::mock('MyClass[getOption]').Chapter 8: Easier Testing With Mockery 120 While there are a few different techniques for mocking global functions. This is precisely when partial mocks come into play. rather than executing the code within it. 7 $mock->fire(). excluding getOption. An alternative option is to make use of passive partial mocks. $mock->shouldReceive('getOption') ->once() ->andReturn(10000). it’s best to avoid this method call altogether. $mock->shouldReceive('getOption') ->once() ->andReturn(10000). when getOption is called. method2]'). which will be mocked and return 10000‘. we simply return 10000. With this technique. 7 $mock->fire(). The previous code snippet may be rewritten as: 1 2 3 4 5 6 public function testPassiveMockExample() { $mock = Mockery::mock('MyClass')->makePartial(). the remainder of the methods on the object will trigger and behave as they normally would. . unless an expectation is specified. 8 9 } In this example. like so: 1 $mock = Mockery::mock('MyClass[method1. simply separate them by a comma. 8 9 } Notice how we’ve placed the method to mock within brackets. Should you have multiple methods. 9 10 11 assertThat($name. 'guitar'. Once you’ve familiarized yourself with the Mockery API. including slight variations that achieve the same end result. it’s recommended that you also leverage the Hamcrest library. $age = 28. 'chess']. assertThat($name. you may use a more human-readable notation to define your tests. is('Jeffrey')). "davedevelopment/hamcrest-php": "dev-master" } Once installed. Below are a handful of examples. is(not('Joe'))). 1 2 3 4 "require-dev": { "mockery/mockery": "dev-master". Like Mockery. which provides an additional set of matchers for defining readable expectations. .Chapter 8: Easier Testing With Mockery 121 Hamcrest The Hamcrest library provides an additional set of matchers for defining expectations. $hobbies = ['coding'. 1 <?php 2 3 4 5 6 7 8 class HamCrestTest extends PHPUnit_Framework_TestCase { public function testHamcrestMatchers() { $name = 'Jeffrey'. it may be installed through Composer. • • • • nullValue integerValue arrayValue rinse and repeat Alternatively. Hamcrest follows the resourceValue naming convention for matching the type of a value.Chapter 8: Easier Testing With Mockery 122 12 assertThat($age. arrayValue()). If using Hamcrest. For instance. 23 } 24 25 } Notice how Hamcrest allows you to write your assertions in as readable or terse a way as you desire. The use of the is() function is nothing more than syntactic sugar to aid in readability. hasKey('coding')). 18 19 assertThat($hobbies. like so: 1 2 3 $mock->shouldReceive('method') ->with(stringValue()) ->once(). is(greaterThan(20))). you might write: 1 2 3 $mock->shouldReceive('method') ->with(Mockery::type('string')) ->once(). is(integerValue())). 20 21 22 assertThat($hobbies. You’ll find that Mockery blends quite nicely with Hamcrest. with Mockery alone. assertThat($hobbies. greaterThan(20)). assertThat($age. Mockery::type may be replaced with stringValue(). . Mockery::any() may become anything(). is(anInstanceOf('Foo'))). string. to specify that a mocked method should be called with a single argument of type. is(arrayValue())). 13 14 15 assertThat($age. 16 17 assertThat(new Foo. to match any argument. Chapter 8: Easier Testing With Mockery 1 2 3 123 $file->shouldReceive('put') ->with('foo.txt', anything()) ->once(); Summary The biggest hurdle to using Mockery is, ironically, not the API, itself, but understanding why and when to use mocks in your testing. The key is to learn and respect the single responsibility principle in your coding workflow. Coined by Bob Martin, the SRP dictates that a class “should have one, and only one, reason to change.” In other words, a class shouldn’t need to be updated in response to multiple, unrelated changes to your application, such as modifying business logic, or how output is formatted, or how data may be persisted. In its simplest form, just like a method, a class should do one thing. The File class manages file system interactions. A MysqlDb repository persists data. An Email class sends emails. Notice how, in none of these example was the word, and used. Once this is understood, testing becomes considerably easier. Dependency injection should be used for all operations that do not fall under the class’s umbrella. When testing, focus on one class at a time, and mock all of its dependencies. You’re not interested in testing them anyways; they have their own tests! Though nothing prevents you from making use of PHPUnit’s native mocking implementation, why bother when Mockery’s improved readability is only a composer update away? Chapter 9: Test Databases There are those who will tell you that under no circumstances should your unit tests touch the database. In fact, I’m a strong proponent for this very thing. If a test can be executed adequately without hitting the database, then absolutely do it. This has the potential to drastically increase the speed of your tests, and is truer to what unit testing is. Having said that, there may be times when this simply isn’t possible for certain tests. Maybe you’re working with a bit of legacy code, or maybe the simple fact of the matter is that you’ll sleep better at night with a handful of tricky unit tests that run against the database. The world won’t end if you break a few rules every once in a while. And, of course, the techniques outlined in this chapter will be useful for other styles of testing, such as integration tests. Before moving forward, a word of caution: if you frequently come to the conclusion that a database call is necessary for your unit tests, then perhaps you should first consider redesigning. Is the class too tightly coupled to the database layer? Test Databases For the instances when it’s necessary to work with a fixture (an object with faked values), you certainly don’t want to be using the production database. Luckily, Laravel can exist and function in multiple environments. For example, when triggering PHPUnit tests, the framework will automatically set the environment to testing. To specify custom configuration options on a per-environment basis, simply create a new folder within app/config/ that has the same name as the environment that its contents should correspond to. Notice that Laravel already includes an app/config/testing folder. As such, any configuration file that is placed here will take precendence over its production sibling. In translation, the contents of app/config/testing/database.php will override app/config/database.php. To specify a unique MySQL database for testing, you might write: 125 Chapter 9: Test Databases 1 <?php // app/config/testing/database.php 2 3 4 return array( 'connections' => array( 5 'mysql' => array( 'driver' => 'host' => 'database' => 'username' => 'password' => 'charset' => 'collation' => 'prefix' => ) 6 7 8 9 10 11 12 13 14 15 'mysql', 'localhost', 'TEST_DB_NAME', 'USERNAME', 'PASSWORD', 'utf8', 'utf8_unicode_ci', '', 16 ) 17 18 ); Don’t forget that, to save time, you can copy and paste the applicable bits from app/config/database.php. In this case, it’s only necessary to update the mysql connection to point to the new test database. That’s all it should take! Specifying the Environment As noted earlier, within the context of a PHPUnit test, the environment will automatically be set to testing. You can confirm this by viewing app/tests/TestCase.php. 1 // app/tests/TestCase.php 2 3 4 5 public function createApplication() { $unitTesting = true; 6 $testEnvironment = 'testing'; 7 8 return require __DIR__.'/../../bootstrap/start.php'; 9 10 } However, the same will not be true when running Artisan commands. Chapter 9: Test Databases 126 For instance, imagine that you’ve added a new migration to create an authors table with a few fields. If using my popular Laravel 4 Generators tool³⁵, this can be accomplished using a single command. 1 2 php artisan generate:migration create_authors_table --fields="name:string, email:\ string:unique" This command will generate the migration, as well as the necessary schema. However, upon migrating the database with php artisan migrate, you’ll find that this still executes the migration on the production database. Of course it does! How was Laravel supposed to know that you expected it to be applied to the testing DB? The --env option may be used to declare your desired environment. 1 php artisan migrate --env="testing" Chances are, though, that you’ll want to refresh the migrations for each test. How might we accomplish that? Calling Artisan From Tests If touching the database, it’s important that each test executes with a clean slate, so to speak. Otherwise, there’s the potential to fall into traps where a particular expectation fails, due to the previous test modifying the database table in a way that you didn’t expect. The solution is to, for each test, roll back the migrations and re-run them. Artisan offers the migrate:refresh command, which will handle this exact task. To trigger these commands from your code (try not to ever do this in your production code), use the Artisan::call() method, like so: 1 <?php // app/tests/models/AuthorTest.php 2 3 4 5 6 class AuthorTest extends TestCase { public function setUp() { parent::setUp(); 7 Artisan::call('migrate:refresh'); 8 } 9 10 } Simply pass the name of an Artisan command as the first argument to Artisan::call(), and it’ll achieve the same end-result as: ³⁵https://github.com/JeffreyWay/Laravel-4-Generators Chapter 9: Test Databases 1 127 php artisan COMMAND --env="testing" You also may need to seed the database as well. 1 $this->seed(); Try It Out Step 1: Create a book-testing database. Step 2: Install the migrations table (only necessary once): 1 php artisan migrate:install --env="testing" Step 3: Add a migration to create a new authors table. 1 2 php artisan generate:migration create_authors_table --fields="name:string, email:\ string:unique"`. This migration will, of course, be used for production, as well as testing. Step 4: Add a dummy test to app/tests/models/AuthorTest.php to ensure that the setUp() method runs at least once. 1 <?php # app/tests/models/AuthorTest.php 2 3 4 5 6 class AuthorTest extends TestCase { public function setUp() { parent::setUp(); 7 Artisan::call('migrate:refresh'); 8 } 9 10 public function testDummyToEnsureSetUpRuns() { $this->assertTrue(true); } 11 12 13 14 15 } Step 5: Run the tests: phpunit If executed successfully, you’ll see that the CreateMigrationsTable migration was executed, and the associated table was added to the test database. Success! You may now rest assured that each test will begin with a freshly reset and migrated database. Here’s an example: 1 2 3 public function setUp() { parent::setUp(). To instruct Laravel to use a database in memory. $this->seed(). 5 'connections' => array( 'sqlite' => array( 'driver' => 'sqlite'.php 2 3 4 return array( 'default' => 'sqlite'.sqlite file. instead. 'prefix' => ''. Though benchmarks vary. 4 Artisan::call('migrate'). this technique may still be one that you should consider. ) ) 6 7 8 9 10 11 12 13 ). Laravel will automatically set the environment to testing and reference the in-memory DB. 5 6 7 } These two lines will construct and seed all necessary tables before firing each test. With this modification alone. a DB in memory was specified.Chapter 9: Test Databases 128 Databases in Memory One popular technique for improving the performance of tests which touch the database is to opt for a Sqlite database in memory. When choosing this method.php to set a new default driver for testing. rather than setting the database key to a . 1 <?php // app/config/testing/database. you re-migrate and seed the database. 'database' => ':memory:'. Notice how. update app/config/testing/database. it’s vital that. . before each test. when testing. First. then it’s likely that your code should be decoupled. use the techniques outlined in this chapter as a last resort. . In a perfect world.Chapter 9: Test Databases 129 Summary When it comes to unit testing specifically. make every effort to test in isolation. If doing so seems impossible. test databases should only be used for outside-in tests. allowing for perfect testability. Laravel . Mockery Every Laravel facade extends a parent Facade class that offers. This makes for the best of both worlds. Laravel will automatically swap out the registered instance with a mock. DB::getQueryLog(). In translation. Other than readability. When called. behind the scenes. In fact. while still. Queue::push().allows for this clean syntax. 1 2 3 Mail::send(). among other things. Those of us with a moderate amount of experience with object-oriented programming and testing are conditioned to instantly squirm at the site of statics. one of the significant advantages to this approach is that the underlying class can easily be swapped out with a mock for testing purposes. but.rather brilliantly. you can test a single static method. it may appear as though Laravel is a big mess of untestable static method calls. Fortunately. this means that you are free to use these facades within your code (without injecting them). See for yourself: .Chapter 10: Just Swap That Thang Laravel’s use of facades is easily one of the most misunderstood features of the framework. I might add . to newcomers. and is what we refer to as the Facade Pattern. using Mockery. Sure. as soon as that method calls another method. you’ll quickly find yourself in a world of hurt when it’s time to test. a shouldReceive method. while still allowing for complete testability. Facade: A facade is an object that provides a simplified interface to a larger body of code. like these. instantiating the applicable class. 25 26 } Don’t worry if this code is difficult to interpret. recognize that the resolved instance is being swapped out with a mocked version. a file will be created on the server. $mock). 22 } 23 24 return call_user_func_array(array($mock. It’s a silly example. So how might we use this to our advantage? Testing For the purposes of simplicity. but will illustrate this swapping functionality nicely. when a particular URL is requested.Chapter 10: Just Swap That Thang 1 131 // vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php 2 3 4 5 6 7 8 9 10 11 /** * Initiate a mock expectation on the facade. When unit testing. Most importantly. 21 static::$app->instance($name. 12 13 14 15 16 17 18 19 20 if (static::isMock()) { $mock = static::$resolvedInstance[$name]. imagine that. we certainly don’t want to physically create the file for each test. func_get_args()). It’s a better idea to mock the File class. You needn’t understand every facet. 'shouldReceive'). } else { static::$resolvedInstance[$name] = $mock = \Mockery::mock(static::getMock\ ableClass($name)). * * @param dynamic * @return \Mockery\Expectation */ public static function shouldReceive() { $name = static::getFacadeAccessor(). though. 1 2 static::$resolvedInstance[$name] = $mock = \Mockery::mock(static::getMockableClas\ s($name)). like so: . when localhost:8000/foo is requested. 1 // app/routes. Somehow. PHPUnit will. you’ll fall into this trap often. Huh? As it turns out. Yet. To avoid repetition. 4 $this->call('GET'. you can place this tearDown method within the parent TestCase class that Laravel provides. fail: 1 2 1) ExampleTest::testCreatesFile Symfony\Component\HttpKernel\Exception\NotFoundHttpException: Let’s create the route.php 2 3 4 Route::get('foo'. 5 6 } This code declares that. function() { 5 6 }). we expect that the put method on the mocked version of the Filesystem class will be called. the test still passes. 'foo'). of course. . the test will now pass. even though the route closure is empty.Chapter 10: Just Swap That Thang 1 2 3 132 public function testCreatesFile() { File::shouldReceive('put')->once(). this call will verify your expectations. Assuming that no foo route currently exists. But how is that possible? We declared that File::put() should be called once. Remember: because we’re leveraging Mockery. Among other things. a tearDown method must always be declared in your test file. you can instead use the facade’s swap method. 'foo'). 1 // app/routes.'/file. as expected.Chapter 10: Just Swap That Thang 1 133 <?php // app/tests/FooTest. 'Lorem ipsum'). Here. while still allowing for the tests to run crazy fast. Let’s make the test pass. The end result is that the production code continues to be as readable as possible.all by simply writing File::shouldReceive(). 10 11 12 13 $this->call('GET'. if you prefer. } 5 6 7 8 9 public function testCreatesFile() { File::shouldReceive('put')->once(). We’re back to green! Admittedly.php 2 3 4 5 6 Route::get('foo'.txt'. function() { File::put(__DIR__. 1 2 3 4 1) ExampleTest::testCreatesFile Mockery\Exception\InvalidCountException: Method put() from Illuminate\Filesystem\\ Filesystem should be called exactly 1 times but called 0 times. As an alternative to using shouldReceive. . }). we see Mockery hard at work.php 2 3 class FooTest extends TestCase { 4 public function tearDown() { Mockery::close(). which will replace the underlying instance with the object that you pass in. we can override the resolved instance of the Filesystem class and replace it with a mocked version . it’s really a very cool (and unique) feature of Laravel! Even though the route’s callback calls File::put(). 14 } 15 16 17 } Now. it’s a silly example. but. nonetheless. we’re back to a failed test. Here’s what the destroy method on a users controller might look like. updating a report. 1 2 3 4 5 public function testDestroyUser() { Event::shouldReceive('fire') ->once() ->with(['cancellation'. 14 15 16 } When writing a functional test for this controller. other parts of the application can respond by sending a cancellation email to the user. 11 12 13 // Redirect to home page return Redirect::home(). '/users/1'). via the Event class (also a facade). Mockery::any()]). 5 // Soft delete the user // To soft delete.com . we’re still able to mock it with ease. 6 7 8 9 10 // Make announcement. clearing out a database record…any number of things. ['user' => $user]). 6 // Perform any other necessary expectations 7 8 $this->call('DELETE'. 1 2 3 4 public function destroy($id) { // Get the user who is canceling $user = $this->user->findOrFail($id). it might make sense for the app to fire an event when a user cancels their subscription. 9 10 } Even though we referenced the Event class explicitly. we can simulate the fired event in the same way that we did with the File facade. This way. set the $softDelete property // on the User model $user->delete(). Using Tuts+ Premium³⁶ as an example. and send the $user object Event::fire('cancellation'.Chapter 10: Just Swap That Thang 134 Mocking Events Laravel 4 provides an easy way to fire and listen for custom events. ³⁶http://tutsplus. allow for the readable static syntax that endeared all of us to Laravel in the first place. which.Chapter 10: Just Swap That Thang 135 Summary In this chapter. if you find yourself relying on mocking facades too much. It’s the best of both worlds! The techniques outlined in this chapter are most useful for small to medium-sized projects. . Even though it feels as if you’re interacting with statics. it might be an indication that your code could be better organized. However. though confusing at first. making for complete testability. these are merely illusions (or facades) that instantiate underlying classes. we reviewed Laravel’s unique use of facades. they prefer integration tests (I’m more inclined to call them functional tests) that will indirectly verify that these controllers work properly. the easier it is to test. we typically do quite a bit more. then a code smell just might be near! Tip: The less work your controller does. in order to proceed in different ways based on the result? If you’re not careful. before long. Well. What allows you to sleep better at night? What Does a Controller Do? Before we discuss testing. let me rephrase that: testing them is a cinch. In these situations. too frequently.Chapter 11: Testing Controllers Testing controllers (a form of functional testing) isn’t the easiest thing in the world. as you might expect. what’s difficult. Are you performing validation in the model? Is that the controller’s responsibility? Are you verifying actions on the model. your controllers can become massive code smells. ensure that the correct database access methods are triggered. it’s best to take a step back. however. is determining what to test. Should a controller test verify text on the page? Should it touch the database? Should it ensure that variables exist in the view? If this is your first hay-ride. in practice. by serving as the application’s HTTP interface. But. and ask yourself. 2. make up your own mind. Controller tests should verify responses. This is true for all classes. and assert that the appropriate instance variables are sent to the view. As they say. at least at first. perhaps it’s a smart idea to first review which responsibilities a controller should have. these things can be confusing! Let me help. Before we continue. Direct traffic. “What is the responsibility of this class?” If you find yourself using the word. . and. and is one of the benefits of embracing the single responsibility principle. rather than ask (Fat Model -> Skinny Controller). As I see things. As always. controllers should tell. it’s worth noting that some groups of developers feel that testing controllers is unnecessary. there are two core areas of focus: 1. Instead. Send its own messages to the domain object. but TestCase. try it out by running phpunit from the command line. Symfony will throw a NotFoundHttpException. If working along. verifying that the stage has been set properly. 'posts'). really) can be divided into three pieces. • Isolate: Mock all dependencies (perhaps excluding the View). Or. . including HttpKernel. and BrowserKit. Here’s the “hello world” of controller testing in Laravel. as well as provides a variety of helper assertion methods that you are encouraged to use. More on that shortly. In the code snippet above. } 5 6 7 8 9 } Laravel leverages a handful of Symfony’s components to ease the process of testing routes and views. This is why it’s paramount that your PHPUnit tests inherit from. but it helps setup the Laravel app for testing. AAA: • Arrange • Act • Assert The Hello World of Controller Testing The best way to learn these things is through examples. or localhost:8000/posts. 1 <?php 2 3 class PostsControllerTest extends TestCase { 4 public function testIndex() { $this->client->request('GET'. not PHPUnit_Framework_TestCase. • Call: Trigger the desired controller method. • Ensure: Perform assertions. Assuming that this line is added to a fresh installation of Laravel.Chapter 11: Testing Controllers 137 3 Steps to Testing Controllers The process of testing a controller (or any class. Don’t worry. we make a GET request to /posts. DomCrawler. you might recognize this as the more generic pattern. Laravel still extends the former. I take things a step further by allowing for such methods as $this->get(). '/'). this essentially translates to. this only requires the addition of a single method.then use $this->client->request(). // Symfony\Component\DomCrawler\Cr\ awler 3 4 $response = $this->call('GET'.php. such as $this->call(). I tried to call that route. will do just fine. but you don’t have anything registered.this bit of text should appear when the page is loaded . '/'). Otherwise. like so: 1 2 3 4 public function testIndex() { $this->call('GET'. which you could add to app/tests/TestCase. } Well. if you intend to crawl the DOM . 1 2 $crawler = $this->client->request('GET'. “Hey. the call method. the returned values will differ. $this->post(). // Illuminate\Http\Response As a basic rule of thumb. that’s only partially true. Thanks to PHP overloading. this type of request is common enough to the point that it makes sense to provide a helper method. in my personal projects. as it will return a Crawler instance.Chapter 11: Testing Controllers 1 2 3 138 $ phpunit 1) PostsControllerTest::testIndex Symfony\Component\HttpKernel\Exception\NotFoundHttpException: In human-speak. fool!” As you can imagine. In fact. Even though $this->call() and $this->client->request() will both call a particular route. Overloading is Your Friend Though we’ll stick with the base functionality in this chapter. which will return a response object. etc. . 'posts'). Laravel does that very thing! This means that the previous example can be refactored. let’s stick with the framework’s base functionality for simplicity’s sake. For example.Chapter 11: Testing Controllers 1 2 3 4 5 6 139 public function __call($method. 'delete'])) { return $this->call($method. Running phpunit again will return us to green. 1 <?php 2 3 4 5 6 Route::get('posts'. 'PostsController@show'. This will trigger the index method on PostsController. $args[0]). the index method of PostsController should pass a . 'patch'. 8 9 } Now. function() { return 'all posts'. To make the test pass. Any applicable parameters may be passed as the third argument. like so: 1 $response = $this->action('GET'. 'put'. ['get'. however. we only need to prepare the proper route. Laravel’s Helper Assertions A test that you’ll find yourself writing repeatedly is one that ensures that a controller passes a particular variable to a view. you’re free to write $this->get('posts') and achieve the exact same result as the previous two examples. 'PostsController@index'). 'post'. Calling Controller Actions If you’d prefer to instead call a controller directly. 1 $response = $this->action('GET'. }). ['id' => 1]). you may use the action method. $args) { if (in_array($method. } 7 throw new BadMethodCallException. As noted above. then getContent() will be equal to the HTML output. Laravel does! Illuminate\Foundation\Testing\TestCase includes a number of methods that will drastically reduce the amount of code needed to perform basic assertions. This list includes: • • • • • • • assertViewHas assertResponseOk assertRedirectedTo assertRedirectedToRoute assertRedirectedToAction assertSessionHas assertSessionHasErrors The following example calls GET /posts and verifies that its views receives the variable. If a View instance is returned from the route.which is returned from $this->call() . This is an important test to write! If it’s that common a task. Optionally. This can be helpful for DOM verifications.” Let’s assume that the posts route consists of: . 5 6 } Tip: When it comes to formatting. wouldn’t it make sense for Laravel to provide a helper assertion to accomplish this very thing? Of course it would. or the returned View. • $response->getContent(): Fetch the evaluated output. you may access the original property directly. once again. such as “the view must contain this string. right? That way. assertViewHas is simply a bit of sugar that inspects the response object . rather than calling the getOriginalContent method. And. I prefer to provide a line break between a test’s assertion and the code that prepares the stage. of course. and display them on the page.Chapter 11: Testing Controllers 140 $posts variable to its associated view. $posts.and verifies that the data associated with the view contains a posts variable. then. 4 $this->assertViewHas('posts'). 'posts'). you have two core choices: • $response->getOriginalContent(): Fetch the original content. When inspecting the response object. the view can filter through all posts. 1 2 3 public function testIndex() { $this->call('GET'. $posts. the test will fail. One thing to keep in mind is that. 4 return View::make('posts. 'posts'). It doesn’t inspect its value. 'foo'). }). it only ensures that the variable. To make it green. though. we simply fetch the posts and pass it to the view. as well as its existence. In this situation. 1 2 3 public function testIndex() { $this->call('GET'. function() { $posts = Post::all(). How might we accomplish that? PHPUnit provides a helpful assertInstanceOf assertion to fill this very need! . 5 6 } With this modified code. 1 2 3 Route::get('posts'. as the code currently stands. ['posts' => $posts]). Should we run phpunit. 4 $this->assertViewHas('posts'. unless the view has a variable.Chapter 11: Testing Controllers 1 141 <?php 2 3 4 5 6 Route::get('posts'. it will squawk with a helpful next step message: 1 2 1) PostsControllerTest::testIndex Failed asserting that an array has the key 'posts'. but instead declare that the value be an instance of Laravel’s Illuminate\Database\Eloquent\Collection class. The assertViewHas optionally accepts a second argument to verify the value of the variable. 5 6 }).index'. it’s likely that we’d rather not specify a value. that is equal to foo. is passed to the view. function() { return View::make('posts.index'). $posts. we want to ensure that Post::all() never fires and hits the database. for basic controller testing. 'posts'). 5 6 // getData() returns all vars attached to the response. in other words. Required Refactoring Unfortunately. Excellent. it will only serve to decrease performance. a SQL query is being executed on the database. Did you catch it? For each test. so it doesn’t require testing. . This section will depend heavily on the Mockery library. 4 $this->assertViewHas('posts'). We’re not interested in testing Eloquent’s ability to fetch records from a database.Chapter 11: Testing Controllers 1 2 3 142 public function testIndex() { $response = $this->call('GET'. It has its own tests. I’ve drilled this into your skull multiple times at this point. $posts).an instance of Illuminate\Database\Eloquent\Collection . and merely verify that the appropriate methods are called with the correct arguments. Taylor knows it works! Let’s not waste time and processing power repeating those same tests. We know that works. $posts = $response->original->getData()['posts'].to the view. 7 8 9 $this->assertInstanceOf('Illuminate\Database\Eloquent\Collection'. integration). Though this is useful for certain kinds of testing (acceptance. 10 11 } With this modification. so far. Please review that chapter if you’re not yet familiar with it. we’ve structured the code in a way that makes it virtually impossible to test. Instead. we’ve declared that the controller must pass $posts . Mocking the Database There’s one glaring problem with our tests so far. it’s best to mock the database. Or. we’ll inject it into the controller’s constructor. which are testable and can be swapped out with mocks (Queue::shouldReceive()). Don’t confuse Laravel’s facades.Chapter 11: Testing Controllers 1 2 3 4 143 Route::get('posts'. Warning: Storing logic within route callbacks is useful for small projects and APIs. 1 2 $ php artisan controller:make PostsController Controller created successfully! Now. but they make testing incredibly difficult. For applications of any considerable size. .index') ->with('posts'. 'PostsController'). use controllers. …and create the necessary resourceful controller with Artisan. This requires some refactoring. Here’s a condensed example that omits all restful methods. $posts). We can't test this!! $posts = Post::all(). The solution is to inject the database layer into the controller through the constructor. with your Eloquent models. rather than referencing the Post model directly. except the one that we’re currently interested in testing. 6 7 8 }). This is precisely why it’s considered bad practice to nest Eloquent calls into your controllers. 5 return View::make('posts. Let’s register a new resource by replacing the posts route with: 1 Route::resource('posts'. function() { // Ouch. laravel. we have the ability to swap it out with a mocked version for testing. Like magic.index') ->with('posts'.com/docs/ioc#automatic-resolution .Chapter 11: Testing Controllers 1 144 <?php 2 3 class PostsController extends BaseController { 4 protected $post. $posts). 5 6 public function __construct(Post $post) { $this->post = $post. Here’s an example of doing just that: ³⁷http://four. Because the model is now injected. Laravel will do this automatically for you! This is referred to as automatic resolution³⁷. rather than reference the Eloquent model. 16 17 } 18 19 20 } Tip:: It’s a better idea to type-hint an interface. itself. Don’t worry about manually injecting the Post instance. This is a significantly better way to structure the code. } 7 8 9 10 11 public function index() { $posts = $this->post->all(). one thing at a time! Let’s work up to that. More on this shortly. 12 13 14 15 return View::make('posts. But. Instead.Chapter 11: Testing Controllers 1 145 <?php 2 3 class PostsControllerTest extends TestCase { 4 public function __construct() { // We have no interest in testing Eloquent $this->mock = Mockery::mock('Eloquent'. 27 } 28 29 30 } The key benfit to this restructuring is that. 'posts'). before the official versions have been loaded. This way. 1 2 3 $this->mock ->shouldReceive('all') ->once(). the database will never needlessly be hit. we have a clean slate to declare . and instead inject the Post model into the controller. 16 17 18 19 20 21 22 $this->app->instance('Post'. 23 24 $this->call('GET'. This is why we hijack both the Post and Eloquent classes within the test’s constructor. now. } 11 12 13 14 15 public function testIndex() { $this->mock ->shouldReceive('all') ->once() ->andReturn('foo'). 25 26 $this->assertViewHas('posts'). $this->mock). which can clash with Mockery. a bit of trickery has to be used in order to get around Eloquent’s use of statics. } 5 6 7 8 9 10 public function tearDown() { Mockery::close(). using Mockery. we merely verify that the all method is triggered on the model. if you choose to forego coding to an interface. 'Post'). Unfortunately. Upon instantiation of the controller. The downside. is that we can’t default to any existing methods. $this->mock). 6 $this->app->instance('Post'. we’d POST to the collection. 11 12 } Assuming that we’re following a restful flavor. Think of this code as saying. 9 10 $this->assertRedirectedToRoute('posts. which just happens to have the same name). or posts (don’t confuse the POST request method with the resource name. . The IoC Container Laravel’s IoC container drastically eases the process of injecting dependencies into your classes. I want you to use my mocked version. like makePartial(). perhaps upon adding a new post to the database. when we need to declare that a mock version of Post should be used for testing.index'). it’s automated! Redirections Another common expectation that you’ll find yourself writing is one that ensures that the user is redirected to the proper location. That’s right. we only need to provide Laravel with the instance of Post that should be used. 7 8 $this->call('POST'. of course. As such. “Hey Laravel. How might we accomplish this? 1 2 3 4 5 public function testStore() { $this->mock ->shouldReceive('create') ->once(). 'posts'). $this->mock). when you need an instance of Post. to add a new post. it is resolved out of the IoC container. you don’t have to write a single binding to allow for this. Laravel leverages the power of PHP reflection to read the typehint and inject the dependency for you. 1 $this->app->instance('Post'. through the use of Mockery methods.” Because the app extends the Container we have access to all IoC methods directly off of it.Chapter 11: Testing Controllers 146 any expectations. Each time a controller is requested. index. Then. There should be two separate paths through the store method. Redirect to the collection. assertRedirectedToRoute. Here’s a modified test that uses Mockery’s with() method to verify the arguments passed to the method referenced by shouldReceive. .index'). via the third parameter to the call method. 'posts'). Even though we aren’t physically submitting a form.Chapter 11: Testing Controllers 1 147 $this->call('POST'. This first path will be for failed validation. posts. You might prefer to also ensure that the $_POST super-global is passed to the create method. Redirect back to the “Create Post” form. each test should represent but one path through your code. 5 6 7 8 9 $this->app->instance('Post'. 1 2 3 public function testStore() { $input = ['title' => 'My Title']. Tip: When a resource is registered with Laravel (Route::resource()). 10 11 $this->call('POST'. we can still allow for this. Run php artisan routes if you ever forget what these names are. $input). As a best practice. $this->mock). the framework will automatically register the necessary named routes. and display the form validation errors. 14 15 } Paths One thing that we haven’t considered in this test is validation. 4 $this->mock ->shouldReceive('create') ->once() ->with($input). 'posts'. or the named route. 12 13 $this->assertRedirectedToRoute('posts. dependent upon whether the validation passes: 1. we only need to leverage another of Laravel’s helper assertions. 2. 14 15 16 } The production code for these two tests might look like: . 10 11 $this->call('POST'. 5 $this->app->instance('Post'.Chapter 11: Testing Controllers 1 2 3 4 148 public function testStoreFails() { // Set stage for a failed validation $input = ['title' => '']. with a success flash message $this->assertRedirectedToRoute('posts. in which case it will merely verify that a message bag has been flashed (in translation. $this->mock). Alternatively. $input). 13 14 15 } The code snippet above explicitly declares which errors should exist. $input). 12 13 // Should redirect to collection.index'. you may omit the argument to assertSessionHasErrors. 6 7 $this->call('POST'. 6 7 8 9 $this->app->instance('Post'. 10 11 12 // The errors should be sent to the view $this->assertSessionHasErrors(['title']). 8 9 // Failed validation should reload the create form $this->assertRedirectedToRoute('posts. your Redirection includes withErrors($errors)). $this->mock). 1 2 3 4 public function testStoreSuccess() { // Set stage for successful validation $input = ['title' => 'Foo Title'].create'). 5 $this->mock ->shouldReceive('create') ->once(). Now for the test that handles successful validation. 'posts'. ['flash']). 'posts'. ['title' => 'required']). and pass an array. From time to time. That way. via the Facade’s shouldReceive method.create') ->withInput() ->withErrors($v->messages()). 16 17 return Redirect::route('posts. As such: 1 Mockery::mock(['fails' => 'true']) will prepare an object. with Mockery.index') ->with('flash'. 18 19 20 } Notice how the Validator is nested directly in the controller? Generally. without us needing to worry about injecting an instance through the constructor. I’d recommend that you abstract this away to a service. .Chapter 11: Testing Controllers 1 2 3 149 public function store() { $input = Input::all(). or a service $v = Validator::make($input. you’ll find that a method that needs to be mocked should return an object. you can test your validation in isolation from any controllers or routes. 'Your post has been created!'). which signals the method name and response value. 4 // We'll run validation in the controller for convenience // You should export this to the model. Nonetheless. respectively. containing a fails() method that returns true. though you certainly could do so. Because this class is a facade. it can easily be swapped out with a mocked version. Win! 1 2 3 Validator::shouldReceive('make') ->once() ->andReturn(Mockery::mock(['fails' => 'true'])). Luckily. this is a piece of cake: we only need to create an anonymous mock. One thing to keep in mind is that we aren’t mocking the Validator. let’s leave things as they are for simplicity’s sake. 5 6 7 8 if ($v->fails()) { return Redirect::route('posts. } 9 10 11 12 13 14 15 $this->post->create($input). itself. If you attempt to inject this interface into your controller. doing so literally requires the modification of a single line. but we’ve added the bare minimum methods for the demo: all. find. Even better. 1 2 3 4 5 6 "autoload": { "classmap": [ // . Mongo or Redis. Laravel will snap at you. the controller doesn’t ever need to be touched. say. .. rather than the Post Eloquent model. 2 3 interface PostRepositoryInterface { 4 public function all(). 9 10 11 } This can certainly be extended.. "app/repositories" ] } When a new class is added to this directory. don’t forget to composer dump-autoload -o.. as a best practice. Here’s the modified PostController. 5 6 public function find($id). What might an interface for managing the database layer of a Post look like? This should get you started. Repositories represent the data access layer of your application. should you perhaps need to swap out Eloquent for.Chapter 11: Testing Controllers 150 Repositories To allow for optimal flexibility.json file for the application to reference it. Go ahead. which has been updated to inject an interface. 7 8 public function create($input). (optimize) flag is optional. try it out and see. like Eloquent. Because this folder is not autoloaded by default. we need to update the composer. 1 <?php namespace Repositories. The considerable advantage to this approach is that. but should always be used. Notice that the repository interfaces are being stored within app/repositories. rather than creating a direct link between your controller and an ORM. The -o. it’s better to code to an interface. and create. we’ll instead make use of service providers to store this sort of logic. of course the framework is squawking! Laravel is smart.151 Chapter 11: Testing Controllers 1 <?php 2 3 use Repositories\PostRepositoryInterface as Post. 7 8 public function __construct(Post $post) { $this->post = $post. For now. declaring that “PostRepositoryInterface is not instantiable. you’ll be met with the dreaded (but beautiful) Whoops error page.php. 4 5 class PostsController extends BaseController { 6 protected $post.” Not Instantiable? If you think about it.index'. ['posts' => $posts]). Later. 14 15 16 17 return View::make('posts. let’s add this binding to app/routes. } 9 10 11 12 13 public function index() { $posts = $this->post->all(). but it’s not a mind reader. 18 } 19 20 } If you run the server and view the output. . It needs to be told which implementation of the interface should be used within the controller. } 18 19 20 21 22 23 } Some might argue that the Post model should be injected into this implementation for testability purposes. use Post. Let’s imagine that. when you need an instance of PostRepositoryInterface. and things should be back to normal. This way. 2 3 4 use Repositories\PostRepositoryInterface. Well. } 13 14 15 16 17 public function create($input) { return Post::create($input). Only. } 8 9 10 11 12 public function find($id) { return Post::find($id). a few months from now. Verbalize this function call as. now. your boss informs you that you need to swap Eloquent out with Redis.Chapter 11: Testing Controllers 1 2 3 4 152 App::bind( 'Repositories\PostRepositoryInterface'. simply inject it through the constructor. 1 <?php namespace Repositories.” app/repositories/EloquentPostRepository will simply be a wrapper around Eloquent that implements PostRepositoryInterface. because you’ve structured your application in this future-proof way. you only need to create the new app/repositories/RedisPostRepository implementation: . and the controller is no longer linked to Eloquent. per usual. your application is far better structured. I want you to use EloquentPostRepository. 'Repositories\EloquentPostRepository' ). we can name the methods however we wish. baby. If you agree. That’s all it should take! Refresh the browser. “Laravel. 5 6 class EloquentPostRepository implements PostRepositoryInterface { 7 public function all() { return Post::all(). we’re not restricting the API (and every other implementation) to Eloquent’s interpretation. it’ll become apparent that this doesn’t scale. Instantly. we’ll PSR-ify our code. 2 3 use Repositories\PostRepositoryInterface. our organization has been a bit lacking.php file? All repositories grouped together in one directory? Sure. that may work in the beginning. 'Repositories\RedisPostRepository' ). . you’re now leveraging Redis in your controller. 4 5 class RedisPostRepository implements PostRepositoryInterface { 6 public function all() { // return all with Redis } 7 8 9 10 11 public function find($id) { // return find one with Redis } 12 13 14 15 16 public function create($input) { // return create with Redis } 17 18 19 20 21 22 } And update the binding: 1 2 3 4 App::bind( 'Repositories\PostRepositoryInterface'.php was never touched? That’s the beauty of it! Structure So far in this lesson. and leverage service providers to register any applicable bindings. Notice how app/controllers/PostsController. but.Chapter 11: Testing Controllers 1 153 <?php namespace Repositories. IoC bindings in the routes. In the final section of this chapter. very quickly. Chapter 11: Testing Controllers 154 PSR-0 defines the mandatory requirements that must be adhered to for autoloader interoperability. 1 2 3 4 5 "autoload": { "psr-0": { "Way": "app/lib/" } } The syntax can be confusing at first. “The base folder for the Way namespace is located in app/lib. rather than grouping all repositories into a repositories directory. replace my last name with the name of your project. It certainly was for me. The only remaining thing to do is update the namespaces for PostRepositoryInterface and EloquentPostRepository. The directory structure to match this would be: • app/ – lib/ * Way/ Next. An easy way to decipher "Way": "app/lib/" is to think to yourself. if we want the autoloading to work as expected. . A PSR-0 loader may be registered with Composer. a more elegant approach might be to categorize them into multiple directories. via the psr-0 object.php It’s vital that we adhere to this naming and folder convention. like so: • app/ – lib/ * Way/ · Storage/ · Post/ · PostRepositoryInterface.php · EloquentPostRepository.” Of course. 2 3 interface PostRepositoryInterface { 4 public function all(). 9 10 11 } And the implementation: 1 <?php namespace Way\Storage\Post. } 7 8 9 10 11 public function find($id) { return Post::find($id). Instead. 2 3 use Post. } 12 13 14 15 16 public function create($input) { return Post::create($input). 7 8 public function create($input). but it makes little sense to store them there permanently. . we’ll use service providers. } 17 18 19 20 21 22 } There we go.Chapter 11: Testing Controllers 1 155 <?php namespace Way\Storage\Post. But what about those pesky bindings? The routes file may be a convenient place to experiment. 5 6 public function find($id). 4 5 class EloquentPostRepository implements PostRepositoryInterface { 6 public function all() { return Post::all(). that’s much cleaner. rather than mocking the Eloquent model. 'Way\Storage\StorageServiceProvider' ) Good. 1 2 3 4 5 6 'providers' => array( 'Illuminate\Foundation\Providers\ArtisanServiceProvider'. hook into an event.php..Chapter 11: Testing Controllers 156 Service providers are nothing more than bootstrap classes that can be used to do anything you wish: register a binding. 'Illuminate\Auth\AuthServiceProvider'. } 7 8 9 10 11 12 13 14 15 16 } To make this file known to Laravel. import a routes file. etc. 'Way\Storage\Post\EloquentPostRepository' ). itself. Updating the Tests With our new structure in place. Here’s an example of one such test: . 4 5 class StorageServiceProvider extends ServiceProvider { 6 // Triggered automatically by Laravel public function register() { $this->app->bind( 'Way\Storage\Post\PostRepositoryInterface'. within the providers array. // . you only need to include it in app/config/app. 2 3 use Illuminate\Support\ServiceProvider. we can instead mock PostRepositoryInterface. 1 <?php namespace Way\Storage. now we have a dedicated file for registering new bindings.. The service provider’s register() method will be triggered automatically by Laravel. 4 $this->mock = $this->mock('Way\Storage\Post\PostRepositoryInterface'). 23 24 } Not bad. $mock). $mock->shouldReceive('all')->once(). $mock). 5 6 } 7 8 9 10 public function mock($class) { $mock = Mockery::mock($class). like so: 1 2 3 public function setUp() { parent::setUp(). it’s better to extract some of this prep work into its own method. 20 $this->call('GET'. 21 22 $this->assertViewHas('posts'). 'posts'). As such. ay? . It stands to reason that every method within PostsControllerTest will require a mocked version of the repository.Chapter 11: Testing Controllers 1 2 3 4 157 public function testIndex() { $mock = Mockery::mock('Way\Storage\Post\PostRepositoryInterface'). 12 13 return $mock. 10 11 } However. 14 15 } 16 17 18 19 public function testIndex() { $this->mock->shouldReceive('all')->once(). 6 7 $this->call('GET'. 'posts'). 8 9 $this->assertViewHas('posts'). we can improve this. 11 $this->app->instance($class. 5 $this->app->instance('Way\Storage\Post\PostRepositoryInterface'. better. 1 <?php 2 3 class PostsControllerTest extends TestCase { 4 5 6 7 8 9 public function tearDown() { Mockery::close(). You can’t get much more readable than that! Allowing for this syntax only requires you to update the Post model. $mock = Mockery::mock($repo). or. $repo = "Way\\Storage\\{$class}\\{$class}RepositoryInterface". you’ll find that this allows for significantly more readable tests. Behind the scenes. if you want to be super-fly. you could even perform your mocking within the Eloquent model! This would allow for: 1 Post::shouldReceive('all')->once().Chapter 11: Testing Controllers 158 Now. and update the IoC binding. 13 14 15 16 } 17 18 } If you can manage the inner “Should I be embedding test logic into production code” battle. func_get_args() ). a BaseModel that all of the Eloquent models extend. and are willing to add a touch of test logic to your production code. } . $mock). 'shouldReceive']. this would mock PostRepositoryInterface. 5 6 7 8 9 10 App::instance($repo. 11 12 return call_user_func_array( [$mock. Here’s an example of the former: 1 <?php 2 3 class Post extends Eloquent { 4 public static function shouldReceive() { $class = get_called_class(). 'posts'. ['flash']). 23 24 $this->assertRedirectedToRoute('posts. 35 36 $this->assertRedirectedToRoute('posts.com/doc/master/components/dom_crawler.html . 29 30 31 32 Post::shouldReceive('create')->once(). doesn’t it? Hopefully. 25 26 } 27 28 public function testStoreSuccess() { $input = ['title' => 'Foo Title']. 'posts'. 37 } 38 39 40 } It feels good. 19 20 21 22 $this->call('POST'. $this->assertSessionHasErrors(). 33 34 $this->call('POST'. your tests will be lightning fast! Crawling the DOM Because Laravel offers Symfony’s DomCrawler³⁸ component out of the box. if needed. this chapter hasn’t been too overwhelming. 14 15 $this->assertViewHas('posts'). 'posts'). you also have the ability to inspect the DOM when calling routes. 16 } 17 18 public function testStoreFails() { $input = ['title' => '']. $input). The key is to learn how to organize your repositories in such a way to make them as easy as possible to mock and inject into your controllers. ³⁸http://symfony.index'. As a result of that effort.Chapter 11: Testing Controllers 159 public function testIndex() { Post::shouldReceive('all')->once(). 10 11 12 13 $this->call('GET'.create'). $input). we expect to see an <h1> tag that contains the text. $this->assertCount(1. Please Login. but stick with the CSSSelector component. 7 8 } Above. '/login'). Fetch By Position 1 $crawler->filter('h2')->eq(2). In the example above. a Crawler object will be returned. Basic Traversing The DomCrawler component offers a handful of traversal methods that can be used for further filtering. Tip: The filter method may be used to filter the DOM. Many of these are simply helpers that could also be accomplished by modifying the CSS selector directly. like so: 1 2 $h1 = $crawler->filter('h1:contains("Hello World!")'). Nonetheless. Ensure View Contains Text 1 2 3 public function testLogin() { $crawler = $this->client->request('GET'. Fetch First or Last . we’re asserting that. when /login is requested. 4 $h1 = $crawler->filter('h1').Chapter 11: Testing Controllers 160 When requesting a URI with $this->client->request(). Alternatively. using the simple CSS selectors that we’re all familiar with. XPath may be used. the assertion could be rewritten. $h1). 5 6 $this->assertEquals('Please Login'. if you can. Its far more readable. the assertion will only pass if the first occurrence of an <h1> tag contains Please Login. they can be a helpful convenience. If you need to be more flexible. which can then be used to filter the DOM and perform assertions. $h1->text()). tasks li')->first(). Fetch Siblings 1 $crawler->filter('. $crawler->filter('ul. Fetch Children 1 $crawler->filter('ul.question')->siblings(). 3 4 5 // Filter down to the desired list $items = $crawler->filter('ul. $tasks = $items->each(function($node. one way would be to use a CSS selector to hunt down the applicable unordered list. 6 7 8 9 10 11 12 // Map over of the list. Capture Text Content Let’s say that you need to fetch the value of every list item within a particular unordered list. and extract the text content for each. Now.tasks')->children(). . '/tasks').tasks li')->last().tasks li'). we’ll see a list of all tasks. }).Chapter 11: Testing Controllers 1 2 161 $crawler->filter('ul. 1 2 // Call route $crawler = $this->client->request('GET'. Nifty. if we var_dump($tasks). map over the list of nodes. 1 2 3 4 5 <ul class="tasks"> <li>Go to store</li> <li>Finish book</li> <li>Eat dinner</li> </ul> How might you do that? Well. $i) { return $node->text(). and return // the text content for each. Chapter 11: Testing Controllers 1 2 3 4 5 6 7 8 162 . we can do better. 4 5 6 7 // Find the form associated with the submit button // that has a value of 'Submit' $form = $crawler->selectButton('Submit')->form(). _text is a special attribute name that refers to the node’s value. rather than mapping over the list items with each(). '/register'). $id = $node->extract('id').array(3) { [0]=> string(11) "Go to store" [1]=> string(11) "Finish book" [2]=> string(10) "Eat dinner" } But. This will achieve the exact same result. Forms Another helpful functionality that the DomCrawler component provides is the ability to fill out and submit forms. $tasks = $items->extract('_text'). Here’s a few examples: 1 2 3 $className = $node->extract('class'). 8 9 10 // Fill out the form $form['first'] = 'Jeffrey'. If you need to grab a different attribute value from a node. The extract method can be used to pull out everything from attribute values. simply substitute _text with its name. to the text content itself. 1 2 3 public function testRegister() { $crawler = $this->client->request('GET'. accordingly. As such. $idAndClass = $node->extract(['id'. like so: 1 2 $items = $crawler->filter('ul. . we can instead clean things up a bit.tasks li'). 'class']). has the benefit of making the tests significantly easier to write and prepare. I prefer to use a more streamlined and readable tool for querying the DOM: Codeception. Setting up a test database (in memory. however. they’d say. however.Chapter 11: Testing Controllers 163 $form['last'] = 'Way'. . verify the routes and view bindings. these things boil down to preference and testing style. As always. from my experiences. Until then. It’s up to you! Make up your own mind. We’ll discuss this tool extensively in one of the following chapters. 13 14 15 } This component can absolutely be helpful in certain situations. One alternate approach. is to remove the mocks and instead treat these as true functional tests. 11 12 // Submit the form $r = $this->client->submit($form). have fun with this! Summary There are those who would argue that testing controllers in this way is unnecessary. Let your functional and acceptance tests. Personally. similar to what the Ruby on Rails community advocates. I like to keep them. if you can) for your controller tests. though slower. Outside of Laravel. Dependency injection is an implementation of the IoC pattern.which. Even better. IoC: An IoC container provides an easy interface for managing the creation of complex objects. it allows you to abstract complicated instantiation behind a single line. While you can certainly leverage dependency injection without the IoC container. without sacrificing on code readability or terseness. Pimple³⁹ is quite popular. as well. makes testability a considerable issue.Chapter 12: The IoC Container An understanding of the IoC (Inversion of Control) pattern is paramount to building flexible and testable applications in Laravel. for testing purposes. Dependency Injection? Laravel’s IoC container makes the process of leveraging dependency injection to build testable applications as easy as possible. rather than hardcoding them . Dependency injection is a pattern for inserting a class’ dependencies through its constructor (or a setter method). it sure does make the task a lot easier! Definition: Think of dependency injection as a way to allow your future self to inject mocks in place of those dependencies. Laravel’s implementation is one of the most powerful in the PHP community. At its core.sensiolabs. Constructor Injection ³⁹http://pimple.org/ . as you might have guessed. along with your desired return value. ⁴⁰https://github. } 4 5 6 7 8 public function fire() { return $this->generator->make() ? 'foo' : 'bar'. It can be improved by injecting an instance of ModelGenerator through the class’ constructor.if not impossible (not without installing Runkit⁴⁰). } 9 10 11 12 13 } Much better! Think of this as the command asking for the ModelGenerator. 5 return $generator->make() ? 'foo' : 'bar'. inject it into the class. } Setter Injection 1 2 3 4 public function setValidator(Validator $validator) { $this->validator = $validator. } Consider the following class: 1 2 3 4 class MyCommand { public function fire() { $generator = new ModelGenerator. 6 } 7 8 } This fire method is difficult to test . 3 public function __construct(ModelGenerator $generator) { $this->generator = $generator. The method can now be tested quite easily: mock ModelGenerator.Chapter 12: The IoC Container 1 2 3 4 165 public function __construct(Validator $validator) { $this->validator = $validator. and perform an expectation that the make method is called.com/zenovich/runkit/ . 1 2 class MyCommand { protected $generator. $command->fire()). Dependency injection may initially be a scary term. In fact. as soon as testability becomes a first-class citizen. Resolving Let’s review a second example.index'. no IoC container is available. 1 <?php 2 3 4 5 6 class UsersController extends BaseController { public function index() { $users = User::all(). There are a couple ways to manage this. Like crossing the streams in Ghostbusters. Consider the following controller. ['users' => $users]). In situations such as this. Which would you prefer? . allowing for testable code also has the adverse effect of forcing a laborious. 8 9 } See? Not too hard. 7 return View::make('users. less readable instantiation. many. for a particular project. 5 $command = new MyCommand($gen). 8 } 9 10 } There’s nothing overly wrong with this code. 6 7 $this->assertEquals('foo'. $gen->shouldReceive('make')->once()->andReturn(true). it’s just not a good idea. how would you test it without physically hitting the database? There’s a couple of hacks that you might use. Why? Well. but they involve alias mocks and multiple PHP processes. many times! However. you’ve surely written it.Chapter 12: The IoC Container 1 2 3 4 166 public function testFire() { $gen = Mockery::mock('ModelGenerator'). but it’s actually quite descriptive: inject dependencies into a class. in some form or another. this code will no longer suffice. Solution 1: Defaults Let’s assume that. 5 6 } We’ll discuss binding/resolving more shortly. while still allowing for constructor injection. rather than referencing the class directly.. those objects will still be instantiated. When testing the method. ['users' => $users]). Eloquent can be avoided all together (why would we have any interest in testing that?) by swapping out the currently registered instance of User with a mock. so let’s solve it the Laravel way! A first step to improving this code might be to first resolve the User model out of the IoC container. if we stick with new Thing. we have the flexibility to inject mock objects for both Dependency and OtherDependency. } With this technique. Because one isn’t currently registered. $this->dep2 = $dep2 ?: new OtherDependency. 1 2 3 public function index() { $users = App::make('User')->all().index'. One solution is to set a sensible default.Chapter 12: The IoC Container 1 167 $thing = new Thing. like so: . 3 4 $thing = new Thing($dep1. Or: 1 2 $dep1 = new Dependency. $dep2). Laravel will simply fetch the User object and return the object. OtherDependency $dep2 = null) { $this->dep1 = $dep1 ?: new Dependency. and then triggers its all method. On the flip-side. The most important piece to understand at the moment is that we’re asking Laravel to resolve an instance of User out of the IoC container. For small projects that don’t have the luxury of a dedicated container (even though they’re freely available on Packagist). Solution 2: Resolving This is a Laravel book. 4 return View::make('users. The following code resolves an instance of User. Here’s an example: 1 2 3 4 5 6 function __construct(Dependency $dep1 = null. this may be a preferable choice. $dep2 = new OtherDependency. } With this modification. then the test may be modified.Chapter 12: The IoC Container 1 2 3 4 5 168 public function testIndex() { App::instance('User'. like so: 1 2 3 4 5 public function testIndex() { $mock = Mockery::mock(). // bar Should you need to perform expectations on the mock (you likely will). $mock->shouldReceive('all')->once()->andReturn('foo'). and their respective values as the methods’ returned values. as the tested code will require access to an all method. $this->call('GET'. Laravel will return this new mock object. the next time that App::make('User') is called. then Mockery will register the keys of that array as methods. 6 $this->call('GET'. We can DRY things up by extracting it to its own method that will run before each test. $mock->foo(). Mockery::mock(['all' => 'foo'])). 'users'). App::instance('User'. we provide one that simply returns foo. Here’s an example: 1 2 $mock = Mockery::mock(['foo' => 'bar']). 7 8 } Repeating this logic for each test can get tedious. though. like so: 1 <?php 2 3 class UsersControllerTest extends TestCase { 4 5 6 7 8 public function tearDown() { Mockery::close(). 'users'). $mock). In this case. } 9 10 protected function mock($class) . Don’t forget that if an array is passed as the first argument to an anonymous mock. and register the // instance with the IoC container $this->mock = Mockery::mock(). 1 <?php // app/routes.Chapter 12: The IoC Container 169 { 11 // Create a mock object. 18 19 20 21 22 23 $this->call('GET'. and then call the route. App Bindings There will likely be times when you need more control over how classes are resolved out of the container (or so you might think). and pass a closure as the second argument. App::bind('foo'. Here’s a contrived example that binds the key. 24 } 25 26 27 } Now. . 'users'). foo. 12 13 14 15 } 16 17 public function testIndex() { // Set an expectation that the all() method // should be called at least once. App::instance($class. You can register a binding with the app. function() { return new IKnowKungFoo. each test only needs to declare its expectations on the model. }). use App::make('foo'). To trigger that closure and return the new object. $this->mock). per usual. Don't do this. which will dictate how this binding will be resolved.php 2 3 class IKnowKungFoo {} 4 5 6 7 8 9 // Only for example. $this->mock->shouldReceive('all')->once(). to a closure that will instantiate the IKnowKungFoo class. When resolving IKnowKungFoo. }). we could instead reference the name of the class to instantiate. then Laravel has no idea what to do. On the other hand. Laravel offers an alternate syntax for binding keys to classes. Rather than passing a closure. Now. 1 App::bind('foo'. function() { var_dump(App::make('foo')). it will simply return a new instance. called IKnowKungFoo. If it finds one. function() { var_dump(App::make('IKnowKungFoo')). we could simply do: 1 2 3 4 Route::get('/'. there’s no need for this. // object(IKnowKungFoo)#161 }). . as a string. The following will achieve the exact same result. in which case a ReflectionException will be thrown: A ReflectionException will be thrown if no binding or class is found. it’ll next look for a class name. Because we’re not performing any real logic within the closure. 'IKnowKungFoo'). if no class exists either.Chapter 12: The IoC Container 1 2 3 4 170 Route::get('/'. if Laravel doesn’t find an existing registered binding. in a real-world situation. which obviously isn’t allowed. Imagine a controller that asks for an implementation of OrderRepositoryInterface. son. K bro?” Remember. the answer is quite frequently . create a new binding to make a connection between the interface and its implementation. Repositories represent the data-access layer of your application. are resolved out of the IoC container. . 1 App::bind('OrderRepositoryInterface'. will come to a road block. }). this line of code is identical to: 1 2 3 4 App::bind('OrderRepositoryInterface'. To reiterate. your controller is requesting an implementation of OrderRepositoryInterface …but you haven’t told me which one to use. Laravel . as it turns out.when you need an instance of OrderRepositoryInterface. Laravel. function() { return new EloquentOrderRepository. To use it. and throw a BindingResolutionException. Perhaps you want to use an Eloquent-specific implementation of the interface: EloquentOrderRepository. when presented with this code. think of this as your way of saying: “Yo. Why? Because it’s attempting to instantiate an interface.particular when coding to an interface. 1 2 3 4 5 6 class OrdersController { public function __construct(OrderRepositoryInterface $order) { $this->order = $order.Chapter 12: The IoC Container 171 Interfaces You might be thinking to yourself. I want you to use EloquentOrderRepository. 'EloquentOrderRepository'). the framework needs a bit more information from us. “When would this ever be useful?” Well. too. Think of this exception as the framework’s way of saying: Okay. In situations such as this. } } Because controllers. I’m adding this to Fail Blog. function() { $repository = new Repositories\User. when the OrdersController is instantiated. …the closure will fire. $validator = new Services\Validators\User. Why is this so useful? Because. Laravel will correctly pull in the Eloquent implementation of the repository. When resolving an object through the IoC container. too much jargon here. Nonetheless. if we run… 1 $usersController = App::make('UsersController'). Example 2: Automatic Resolution Let’s review a second example to see just how far the rabbit hole goes. and return a new instance of UsersController with those two dependencies. Fine . you may continue to throw this code in your routes file. wait: Laravel is a smart little cookie! Through a technique referred to as automatic resolution. by allowing for this level of flexibility. when testing controllers (which you’ll soon learn how to do). thanks to the power of PHP reflection. Laravel will attempt to read the constructor’s type-hints and automatically inject the instance for you. let’s simplify things. however. Okay.we already know this. for real-world projects. $validator). you’ll likely want to create a service provider to register these types of bindings. 5 return new UsersController($repository. injecting a mocked version of the repository will be a cinch! Tip: An existing instance may be bound to the container with App::instance(). But. 6 7 }). For experimentation purposes.Chapter 12: The IoC Container 172 Now. What if a UsersController class requires two dependencies: a repository and user validation service? 1 2 3 4 App::bind('UsersController'. Laravel can do the brunt of the work for you. . the build method. classes and methods. have a look at Illuminate/Container/Container .php 3 4 5 6 7 8 9 10 11 12 13 14 /** * Instantiate a concrete instance of the given type. $parameters = array()) { // If the concrete type is actually a Closure. no manual binding necessary.” . we will just // execute it and hand back the results of the functions. this is a very cool thing. Validator $validator) { 4 } 5 6 } When resolved.php.net⁴¹ Consider the following constructor: 1 2 3 class UsersController { public function __construct(UserRepository $user. ⁴¹http://www.specifically. Laravel will detect that instances of both UserRepository and Validator are required. It will then instantiate and inject them automagically! Folks. See below (but don’t worry if it’s overwhelming). * * @param string $concrete * @param array $parameters * @return mixed */ public function build($concrete. functions. 1 2 <?php // vendor/laravel/framework/src/Illuminate/Container/Container. the reflection API offers ways to retrieve doc comments for functions. Extra Credit If you’d like to learn more about how Laravel allows for this. I repeat: Laravel will automatically inject instances of those two classes for you.reflection. Additionally.Chapter 12: The IoC Container 173 Definition: “PHP 5 comes with a complete reflection API that adds the ability to reverse-engineer classes. interfaces.php .php.net/manual/en/intro. methods and extensions. that means there are no dependencies then we can just resolve the instances of the objects right away. } 15 16 17 18 19 20 21 $reflector = new \ReflectionClass($concrete). 48 49 50 51 52 53 return $reflector->newInstanceArgs($dependencies). 43 } 44 45 $parameters = $constructor->getParameters(). 32 } 33 34 $constructor = $reflector->getConstructor(). 35 36 // // // // if { 37 38 39 40 41 42 If there are no constructors. the developer is attempting to resolve an abstract type such as an Interface of Abstract Class and there is no binding registered for the abstractions so we need to bail out. without resolving any other types or dependencies out of these containers. ( ! $reflector->isInstantiable()) $message = "Target [$concrete] is not instantiable. if ($concrete instanceof Closure) { return $concrete($this. 46 47 // Once we have all the constructor's parameters we can create // each of the dependency instances and then use the reflection // instances to make a new instance of this class. injecting the // created dependencies in. $dependencies = $this->getDependencies($parameters). 22 23 // // // // if { 24 25 26 27 28 29 If the type is not instantiable.Chapter 12: The IoC Container // which allows functions to be used as resolvers for more // fine-tuned resolution of these objects. (is_null($constructor)) return new $concrete. 30 31 throw new BindingResolutionException($message). 54 55 } The end result is that the previous binding example is unnecessary: 174 .". $parameters). function() { $repository = new Repositories\User. $validator). It’s only necessary in situations when the instantiation process is a bit too specific for Laravel to figure out automatically. When embraced. Use it. 6 return new UsersController($repository.Chapter 12: The IoC Container 1 2 3 4 5 175 // Unnecessary App::bind('UsersController'. Laravel will automatically do this for you. Make it easy on yourself: Laravel’s IoC container is simply a tool that reverses the direction of responsibility. Summary When researching inversion of control on the internet. 7 8 }). you’ll likely come across dozens of incredibly confusing articles and definitions. . $validator = new Services\Validators\User. your applications will become significantly more flexible and testable. by assisting with class dependency management. com/doc/2. which provides. but great flexibility and configuration for your commands.html . the available functionality was somewhat limited.'. version four instead leverages the powerful Symfony Console component⁴². use Symfony\Component\Console\InputInputOption. to create the skeleton for a command that expedites the process of adding new users to a users table. 11 12 public function __construct() ⁴²http://symfony. 9 10 protected $description = 'Command description. For instance.php with the following boilerplate: 1 <?php 2 3 4 5 use Illuminate\Console\Command.0/components/console/introduction. Scaffolding Artisan ships with a nifty generator that can scaffold the necessary boilerplate code for new commands. let’s first review the basic process of creating them. from the command-line. Luckily. I think you’ll love it! Commands 101 Before we dig into testing commands.Chapter 13: Test-Driving Artisan Commands Exercise Though Laravel 3 offered a basic means for executing tasks directly from the command line. use Symfony\Component\Console\InputInputArgument. not only structure. 6 7 8 class UserCreatorCommand extends Command { protected $name = 'command:name'. run: 1 php artisan command:make UserCreatorCommand This will generate app/commands/UserCreatorCommand. } 30 31 32 33 34 35 36 37 38 39 40 41 42 } Take note of the name and description properties: 1 2 protected $name = 'command:name'. 'An example option. Tip: The console component also supports pseudo-namespacing. null ) ).Chapter 13: Test-Driving Artisan Commands Exercise 177 { 13 parent::__construct().'. InputOption::VALUE_OPTIONAL. null. When running php artisan. InputArgument::REQUIRED.' ) ). etc. 14 } 15 16 public function fire() {} 17 18 protected function getArguments() { return array( array( 'example'. To place multiple commands under a migrate heading. } 19 20 21 22 23 24 25 26 27 28 29 protected function getOptions() { return array( array( 'example'. protected $description = 'Command description. . as Laravel does by default. they will be grouped. you’d name them as migrate:install. The values provided here will be referenced when running php artisan from the command line.'. migrate:make. 'An example argument. 1 <?php // app/start/artisan.php. Specify any available arguments within the getArguments method. like so: .'. we might go with: 1 2 protected $name = 'user:create'. the user:create command should now display at the bottom.Chapter 13: Test-Driving Artisan Commands Exercise 178 For a user creator command. protected $description = 'Create a new record in the users table. That should do it! If we now list the available Artisan commands with php artisan. it first has to be registered with Artisan.php 2 3 Artisan::add(new UserCreatorCommand). adjusting these values alone won’t make the command show up when running php artisan. which can be done from app/start/artisan. It’s so easy! List available commands with php artisan. Arguments The console component makes the process of passing arguments and options to your command a cinch. However. if we attempt to run the command without arguments. * * @return array */ protected function getArguments() { return array( array( 'name'. A RuntimeException will be thrown if no argument is passed. The fire method on the class will automatically be triggered when the command is called from the commandline. 'Name of the user' ) ). To capture the value of the supplied name from your code. and printing the response to the console. via InputArgument::REQUIRED (the inverse being InputArgument::OPTIONAL). } Because this code specifies that the name argument is required. Here’s an example of capturing the name argument. Should you require additional arguments. a RuntimeException will be thrown. InputArgument::REQUIRED. .Chapter 13: Test-Driving Artisan Commands Exercise 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 179 /** * Get the console command arguments. use $this->argument(KEY). simply return a comma-separated list of arrays from the getArguments method. The users of your command can view a list of the arguments and options. error(). 1 php artisan help user:create 180 . info().Chapter 13: Test-Driving Artisan Commands Exercise 1 // app/commands/UserCreatorCommand. via the help dialog. 12 13 } Tip: The Command class provides a handful of helper methods to print strings to the console. but not limited to."). Refer to the Illuminate\Console\Console class for a full list. * * @return void */ public function fire() { $name = $this->argument('name'). including. 11 $this->info("The name that you want to add is {$name}. and line().php 2 3 4 5 6 7 8 9 10 /** * Execute the console command. like so: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * Get the console command options.Chapter 13: Test-Driving Artisan Commands Exercise 181 Use the help dialog to view accepted arguments and options. 'Path to models directory'. Perhaps your model generator (which we’ll build using TDD in the next section) should accept an optional path to the models directory. via the getOptions method. * * @return array */ protected function getOptions() { return array( array( 'path'. InputOption::VALUE_OPTIONAL. Options Options are just that: optional. 'p'. You can accept this value. 'app/models' ) ). They can be helpful for specifying flags which alter how a particular action is executed. } Each option array can accept five parameters: . 1 // app/start/artisan. Single Responsibility Principle It’s important to remember that. the value provided should be used as the path to the models directory. You may inject these dependencies from app/start/artisan. Yes. . VALUE_REQUIRED. A command class is responsible for running a command. Here’s an example. or modifying a database record. we come back to the single responsibility principle. VALUE_NONE. you can make the class handle that work. so that the class can merely call the necessary methods on its dependencies. Instead. but try not to. while injecting an instance of Laravel’s Filesystem class.php. including VALUE_OPTIONAL.php 2 3 4 5 Artisan::add( new ModelGeneratorCommand($app['files']) ). make use of dependency injection. once again. doing so is considered bad practice.Chapter 13: Test-Driving Artisan Commands Exercise • • • • • 182 Name of the option Optional alias: --path becomes -p Option variants. the user may instead reference the alias that we specified: 1 php artisan generate:model Dog -p="app/foo/models" If the path option is specified. or compiling templates. while you can throw every ounce of logic into your command class. VALUE_IS_ARRAY Description for help dialog Optional default value Updating this single method now allows the user to override the default path to the models directory. Why? Well. otherwise. our default of app/models will be used. Below is an example of registering a ModelGeneratorCommand command. assuming a generate:model command: 1 php artisan generate:model Dog --path="app/foo/models" Alternatively. it shouldn’t be concerned with manipulating the file system. ).Chapter 13: Test-Driving Artisan Commands Exercise 183 Exercise Now that the basics of creating commands are instilled. 'email' => 'jeffrey@example. when Laravel scaffolds a new Composer package for you. Next. These values will be used. Create the Package Before calling Laravel’s workbench command. feel free to use any identifier. we’ll create a package that allows the user to rapidly generate a new model. running php artisan help workbench. vendor will be the name of your company. let’s move on to the chapter exercise. using a base template as its contents.com/JeffreyWay/Laravel-4-Generators .php with your name and email address. Chances are high that. push it to GitHub. This means that we’ll need to follow a slightly different process than was covered earlier in this chapter. we can see that the command expects one argument: the name of the vendor and package. using Laravel’s helpful workbench. Let’s try it: ⁴³https://github. and make it available on Packagist. when writing new commands. update app/config/workbench.com'. first.php 2 3 4 5 6 return array( 'name' => 'Jeffrey Way'. 1 2 $ php artisan help workbench package The name (vendor/name) of the package. The easiest way to do so is to create a package. 1 <?php // app/config/workbench. We’ll use test-driven development to build a file generator that is similar to my popular Laravel Generators tool⁴³. More specifically. Typically. or even your last name. you’ll want to share them. Alternatively. as I do. However. this time. we need to specify a path to to the package directory. as we don’t want the command to be placed within app/commands. we’ll let Laravel handle the process of generating the boilerplate code for our new model generator command. .Installing illuminate/support (dev-master cc16ed1) Cloning cc16ed1445c39846c1c9aaccfc9f1648b28bf3c3 7 8 9 Writing lock file Generating autoload files This will generate a new workbench directory. 1 2 php artisan command:make ModelGeneratorCommand --path="workbench/way/generators/s\ rc/Way/Generators/Commands" For now. Laravel’s workbench allows you to rapidly build new packages.Chapter 13: Test-Driving Artisan Commands Exercise 1 2 3 4 5 6 184 $ php artisan workbench way/generators Package workbench created! Loading composer repositories with package information Installing dependencies (including require-dev) . Generating the Command Next. along with the necessary folder structure (following PSR-0) and files for rapidly preparing your package. we’ll only add a name and description. the command:make command accepts a --path option that allows for this. Luckily. as we did earlier in the chapter. php 2 3 4 protected $name = 'generate:model'. Definition: Service providers are simply bootstrap classes for packages.php to register the command with Artisan.. though.php 2 3 namespace Way\Generators. This time. 1 <?php // workbench/. 13 $this->commands( 'generate. GeneratorsServiceProvider./Way/Generators/Commands/ModelGeneratorCommand. protected $description = 'Generate a new model. we register the package’s Artisan commands.'./Way/Generators/GeneratorsServiceProvider. 6 7 8 class GeneratorsServiceProvider extends ServiceProvider { protected $defer = false. we’ll store the instantiation process within a service provider. In the code below.model' ). Service Providers In the previous section.. When Laravel scaffolded your new package. we used app/start/artisan.. 14 15 16 17 } 18 19 20 protected function registerModelGeneratorCommand() { . they contain two methods: boot and register. via the commands method on the ServiceProvider class. 9 10 11 12 public function register() { $this->registerModelGeneratorCommand(). or anything else you wish to do.. By default.Chapter 13: Test-Driving Artisan Commands Exercise 1 185 // workbench/. attach to events. Within these methods you may do anything you like: include a routes file. register bindings in the IoC container. it included a service provider class. Think of this file as the bootstrap for your package. 4 5 use Illuminate\Support\ServiceProvider. ...php 2 3 4 5 6 'providers' => array( // . 21 22 23 24 } 25 26 186 } The final step .php. 1 // app/config/app.php.one that every user of the package must perform manually .model'] = $this->app->share(function($app) { return new ModelGeneratorCommand.Chapter 13: Test-Driving Artisan Commands Exercise $this->app['generate. you’ll want to make a note in your readme file that the user needs to add the necessary service provider to app/config/app.. 'Way\Generators\GeneratorsServiceProvider' ) All service providers listed here will automatically be loaded by Laravel.is to add the service provider to the providers array in app/config/app. }). It should now work! Run php artisan to ensure that it does. Tip: If distributing this package through Packagist. As such.0".json file (not the master composer. the Symfony team have already considered this.x" }.0. Now that you’re comfortable with the basic workflow for creating commands and packages. "require-dev": { "mockery/mockery": "dev-master" } This specifies that the package expects to have access to Illuminate’s console component.0. "illuminate/console": "4. When the workbench command generated the package. luckily. the tests for this class will be fairly simple. Testing output to a console can get a bit tricky with PHPUnit.php file. as well as Mockery to behave as expected. though.Chapter 13: Test-Driving Artisan Commands Exercise 187 Testing Artisan Commands So far in this chapter. we haven’t performed an ounce of testing.json file).3. Next. like so: 1 2 3 4 5 6 7 8 "require": { "php": ">=5.x". "illuminate/support": "4. we’ll write the first test. Within that directory. add a new commands/ModelGeneratorCommandTest. Their console component provides a test helper that eases the process . it also included a folder for any tests. Before moving forward. Open the package’s composer. and update the require objects. Run composer update to pull in both of these. Notice how the directory structure of our tests mirror the production files. let’s fix that! The truth is that a command class shouldn’t be responsible for too much. we first need to pull in two dependencies: illuminate/console and mockery. but. we only need to print the necessary string. 11 12 13 $this->assertEquals( "The name argument is foo\n".php 2 3 4 use Way\Generators\Commands\ModelGeneratorCommand. . behind the scenes. $tester->getDisplay() ). we could write: 1 <?php // workbench/. Lastly. the getDisplay method on the CommandTester instance is used to fetch any output. Eventually. 10 # Pass in any arguments or options $tester->execute(['name' => 'foo']). running phpunit will. to test that a dummy command simply outputs The name argument is foo. use Symfony\Component\Console\Tester\CommandTester. ModelGeneratorCommand. from the ModelGeneratorCommand command. --. of course. 1 2 3 4 5 6 7 1) ModelGeneratorCommandTest::testOutput Failed asserting that two strings are equal.Chapter 13: Test-Driving Artisan Commands Exercise 188 considerably. 14 15 16 17 } 18 19 } The most important bit of this code is that we instantiate Symfony’s CommandTester class.in this case.. In its current state. 5 6 7 8 9 class ModelGeneratorCommandTest extends PHPUnit_Framework_TestCase { public function testOutput() { $tester = new CommandTester(new ModelGeneratorCommand).. and pass in an instance of the command that we wish to test . providing any applicable arguments./tests/commands/ModelGeneratorCommandTest. fail. We then trigger its execute method. the fire method on your command will be triggered. For example.Expected +++ Actual @@ @@ -'The name argument is foo +'' To make it pass. use Mockery as m. In this case. } And that returns us to green! Congratulations. It forces you to think before you write. Perhaps the generator will return a boolean./Generators/Commands/ModelGeneratorCommand. it’s always a smart idea to determine what you expect to happen. let’s use Mockery to prepare the first test: testGeneratesModelSuccesfully. $gen->shouldReceive('make') .. $this->argument('name')). Planning Before writing a test. and then pass that data on to a special model generator class that will take care of the rest.php 2 3 4 5 use Way\Generators\Commands\ModelGeneratorCommand. 17 18 19 20 # We only want to ensure that its make() method is called.. Expectations Now that we have a game plan. 1 <?php // workbench/. } 12 13 14 15 16 public function testGeneratesModelSuccessfully() { # We're not interested in testing the model generator yet. 6 7 8 9 10 11 class ModelGeneratorCommandTest extends PHPUnit_Framework_TestCase { public function tearDown() { m::close(). we limit the command class’s responsibility../tests/commands/ModelGeneratorCommandTest.. when the command fires. I expect it to fetch the path and model name to generate.Chapter 13: Test-Driving Artisan Commands Exercise 1 189 // workbench/. use Symfony\Component\Console\Tester\CommandTester. This way.php 2 3 4 5 6 public function fire() { $this->info('The name argument is ' . $gen = m::mock('Way\Generators\Generators\ModelGenerator'). # We'll have it return true to mimic a positive outcome. indicating whether or not the model was created successfully. you’ve just tested your first dummy command. That’s one of the core benefits to a TDD cycle. . we can easily swap out the generator with the mocked version. Illuminate\Console\Command. $this->assertEquals( "Created app/models/Foo. $tester->getDisplay() ). 30 31 32 33 34 } 35 36 } Dependency Injection The first step to making this test pass is to ensure that the generator can be injected into the ModelGeneratorCommand class through its constructor.php') ->andReturn(true). as illustrated above.php 2 3 <?php namespace Way\Generators\Commands. 4 5 6 7 8 use use use use Way\Generators\Generators\ModelGenerator. 12 13 protected $description = 'Generate a new model./Generators/Commands/ModelGeneratorCommand. 9 10 11 class ModelGeneratorCommand extends Command { protected $name = 'generate:model'.Chapter 13: Test-Driving Artisan Commands Exercise 190 ->once() ->with('app/models/Foo. 1 // workbench/. 25 26 $tester = new CommandTester($command).php\n". This way.'. 16 17 18 19 public function __construct(ModelGenerator $generator) { parent::__construct(). 14 15 protected $generator. Symfony\Component\Console\Input\InputArgument. Symfony\Component\Console\Input\InputOption. $tester->execute(['name' => 'foo']). 27 28 29 # Ensure that the proper response is printed.. 21 22 23 24 $command = new ModelGeneratorCommand($gen). . Remember: we’re testing the command class.. 21 } // .. Symfony\Component\Console\Input\InputOption../Generators/Generators/ModelGenerator. Give the fire method below close inspection. so don’t double-up! Making the Test Pass To make the test pass. Symfony\Component\Console\Input\InputArgument. we need to calculate the proper path to the model that should be created.. Illuminate\Console\Command. not the generator itself. 22 23 24 } Generator Class The next step is to create the generator class that will handle the process of creating the model with the necessary boilerplate code.'. 12 13 protected $description = 'Generate a new model. 9 10 11 class ModelGeneratorCommand extends Command { protected $name = 'generate:model'.Chapter 13: Test-Driving Artisan Commands Exercise 191 20 $this->generator = $generator. 1 <?php // workbench/. 1 // workbench/. . and send that on to the generator class. This will be placed in the Way/Generators/Generators directory. 4 5 6 7 class ModelGenerator { public function make() {} } That will do for now.php 2 3 <?php namespace Way\Generators\Commands..php 2 3 namespace Way\Generators\Generators. That will have its own tests. 4 5 6 7 8 use use use use Way\Generators\Generators\ModelGenerator./Generators/Commands/ModelGeneratorCommand.. 21 22 } 23 24 25 26 public function fire() { $path = $this->getPath(). ). } 39 40 41 42 43 44 45 46 47 48 49 protected function getArguments() { return array( array( 'name'. 27 if ($this->generator->make($path)) { $this->info("Created {$path}"). '\ . InputArgument::REQUIRED. 16 17 18 19 public function __construct(ModelGenerator $generator) { parent::__construct(). '/' .php'.' ). .Chapter 13: Test-Driving Artisan Commands Exercise 192 14 15 protected $generator. } 50 51 52 53 54 55 protected function getOptions() { return array( array( 'path'. ucwords($this->argument('name')) . 20 $this->generator = $generator. 'Name of the model to generate. } 28 29 30 31 32 } 33 34 35 36 37 38 protected function getPath() { return $this->option('path') . . InputOption::VALUE_OPTIONAL. assuming that the file was generated successfully. 17 18 19 20 # If generation failed./tests/commands/ModelGeneratorCommandTest. what if the model generation was not successful? We need a test for that.php 2 3 4 5 public function testAlertsUserIfModelGenerationFails() { $gen = m::mock('Way\Generators\Generators\ModelGenerator'). $this->assertEquals( "Could not create app/models/Foo. . 14 15 16 $tester = new CommandTester($command). and the necessary response is printed to the console..'. 'Path to the models directory. phpunit should return green! The reason is because the generator is being mocked with Mockery. 61 } 62 63 193 } Even though we haven’t yet built the generator class beyond its skeleton. $tester->execute(['name' => 'foo']). simulate a failed result $gen->shouldReceive('make') ->once() ->with('app/models/Foo. 1 // workbench/. But.Chapter 13: Test-Driving Artisan Commands Exercise null. 6 7 8 9 10 11 # This time. 12 13 $command = new ModelGeneratorCommand($gen).php') ->andReturn(false). the output should indicate as much. 'app/models' 56 57 58 59 ) 60 ). too! Tip: Always provide a test for each path through your code.php\n". The test merely ensures that the make method on the generator class is called. 7 8 9 10 11 $command = new ModelGeneratorCommand($gen). as the code stands. if a custom path option is specified by the user.Chapter 13: Test-Driving Artisan Commands Exercise $tester->getDisplay() 21 ).php'). '--path' => 'app/foo/models'])... 22 23 194 } Making this test pass is a cinch. this will already work.if only for peace of mind. but always feel free to write additional tests . $tester->execute(['name' => 'foo'./tests/commands/ModelGeneratorCommandTest. 12 13 } The final test for this class will assert that. } 7 8 9 10 11 $this->error("Could not create {$path}")..php 2 3 4 5 public function fire() { $path = $this->getPath()..php 2 3 4 5 public function testCanAcceptCustomPathToModelsDirectory() { $gen = m::mock('Way\Generators\Generators\ModelGenerator'). accordingly. the getPath method will respond. 1 // workbench/. 1 // workbench/. 12 13 $tester = new CommandTester($command). 6 if ($this->generator->make($path)) { return $this->info("Created {$path}"). 6 # Ensure that the custom path to the directory is correct $gen->shouldReceive('make') ->once() ->with('app/foo/models/Foo. I’m fairly certain that./Generators/Commands/ModelGeneratorCommand. 14 15 16 } . . we. the ModelGeneratorCommand class (along with its associated tests) is finished! But. . use Illuminate\Support\ServiceProvider. }). phpunit still returns green! The only remaining thing to do is ensure that.. of course. itself. certainly. we’re not done. 7 8 9 class GeneratorsServiceProvider extends ServiceProvider { protected $defer = false. Testing the Model Generator To begin testing the model generator./Way/Generators/GeneratorsServiceProvider. 10 public function register() { $this->registerModelGeneratorCommand(). 3 4 5 6 use Way\Generators\Commands\ModelGeneratorCommand. 11 12 13 14 $this->commands( 'generate. because we manually injected a mocked version of the model generator class. need a new test class.model' ).Chapter 13: Test-Driving Artisan Commands Exercise 195 Without a single change. So far. we need to test the generator. 1 2 <?php // workbench/. which can be stored within the tests/generators/ directory.php namespace Way\Generators. Remember: we got away without doing this. we’ve only mocked the model generator for the purposes of testing the command. use Way\Generators\Generators\ModelGenerator.model'] = $this->app->share(function($app) { # Inject the generator into the command return new ModelGeneratorCommand(new ModelGenerator). when the ModelGeneratorCommand class is registered with Artisan in the service provider. Next. 15 16 17 } 18 19 protected function registerModelGeneratorCommand() { $this->app['generate. we also inject the ModelGenerator class. } 20 21 22 23 24 25 26 27 28 } And with that. use Mockery as m.3. 1 // tests/generators/ModelGeneratorTest.php 2 3 public function testCanGenerateModelUsingTemplate() {} We must be careful that our tests don’t actually create new files. we’ll add a tests/generators/stubs directory.0. "illuminate/support": "4. for the purposes of this tutorial.0. "illuminate/filesystem": "4.json file.Chapter 13: Test-Driving Artisan Commands Exercise 1 196 <?php // tests/generators/ModelGeneratorTest. This serves no purpose other than to slow down the tests. } } The primary purpose of this class is to facilitate the process of compiling a model template with the values provided by the user. in order to make use of that class in our package. To pull in this package. .0. and run composer update.php 2 3 4 use Way\Generators\Generators\ModelGenerator.0". This means that we’ll need to mock Laravel’s Filesystem class. the first test will be called testCanGenerateModelUsingTemplate.x". Don’t forget that Laravel is composed of multiple components.x" } Next. 5 6 7 8 9 10 11 class ModelGeneratorTest extends PHPUnit_Framework_TestCase { public function tearDown() { m::close().txt stub. This folder will only house a model. With that in mind. it needs to be required as a dependency. however. simply require it within the package’s composer. To allow for this.x". 1 2 3 4 5 6 "require": { "php": ">=5. "illuminate/console": "4. we need some means to verify that the compiled template matches our expected output. and physically creating the file within the proper models directory. and ensure that its put method is called with the correct path and data.txt\ ')). $generator->make('app/models/Foo. Running the tests. we expect that the put method on Laravel’s helpful Filesystem class will be called. we can simply mock the Filesystem. when the make method on the model generator is called. accordingly. and the contents of the model stub that we created earlier. Now. file_get_contents(__DIR__. The code above specifies that. More importantly. 1 // tests/generators/ModelGeneratorTest.'/stubs/model.php'.php').Chapter 13: Test-Driving Artisan Commands Exercise 1 197 <?php // tests/generators/stubs/model. as you might expect. All unlisted methods will continue to behave as they normally would.txt 2 3 class Foo extends Eloquent { 4 5 } The contents of this file represent the output that we expect to be written. though. by specifying a comma separated list of methods to mock within brackets. will return red. when php artisan generate:model foo is executed. 6 7 8 9 10 $file->shouldReceive('put') ->once() ->with('app/models/Foo. it should be called with the correct arguments: the name of the model to generate.php 2 3 4 5 public function testCanGenerateModelUsingTemplate() { $file = m::mock('Illuminate\Filesystem\Filesystem[put]'). 12 13 14 } Tip: Mockery allows for partial mocks. 11 $generator = new ModelGenerator($file). . php 2 3 namespace Way\Generators\Generators.php".. when running the command. we only need to write the necessary logic to fetch the compiled template. the two should match! At this point. 6 7 8 class ModelGenerator { protected $file.. 9 10 public function __construct(File $file) . we need a new templates directory that will house a stub for each generator (we only have one for this chapter. {{name}} will be replaced with Foo.Chapter 13: Test-Driving Artisan Commands Exercise 1 198 $ phpunit 2 3 4 1) ModelGeneratorTest::testCanGenerateModelUsingTemplate Mockery\Exception\InvalidCountException: Method put("app/models/Foo./Generators/Generators/ModelGenerator. 1 <?php // workbench/. we have a link between the model template and the test stub. Now. If it was compiled correctly. and pass its contents on to the Filesystem’s put method. Assuming php artisan generate:model foo. It seems that Mockery expected the put method to be called with the proper arguments. but you could certainly add more on your own). "<?php 5 6 class Foo extends Eloquent { 7 8 9 }") from Illuminate\Filesystem\Filesystem should be called exactly 1 times but called 0 times. Give the following snippet some study time. 1 <?php // workbench/... In order to make the test pass. Here’s the template for generating a new model.txt 2 3 class {{name}} extends Eloquent { 4 5 } Notice the {{name}} portion? Think of it as a placeholder that will ultimately be replaced with the model name designated by the user./Generators/Generators/templates/model. but that never occurred. 4 5 use Illuminate\Filesystem\Filesystem as File. which we’ve mocked for the test. } 20 21 22 23 24 return false. $template). $name. 25 } 26 27 protected function getTemplate($name) { $template = $this->file->get(__DIR__. As noted earlier. That’s all it takes! We’re back to green! Don’t forget. $template). 28 29 30 31 return str_replace('{{name}}'. though. 15 16 17 18 19 if (! $this->file->exists($path)) { return $this->file->put($path. in fact. '. 12 } 13 14 public function make($path) { $name = basename($path.txt'). we merely need to ensure that the appropriate method was. using the user’s provided model name.php'). The getTemplate method fetches the model template and runs a quick search and replace on it. we need to update the service provider to pass it in. we don’t want to physically create this file. The results of this operation will be passed on to the Filesystem’s put method. .Chapter 13: Test-Driving Artisan Commands Exercise 199 { 11 $this->file = $file. called. that.'/templates/model. $template = $this->getTemplate($name). 32 } 33 34 } The code is really quite simple. because the model generator expects an instance of Filesystem upon instantiation. Great work! . 9 }).php 2 3 4 5 6 7 protected function registerModelGenerator() { $this->app['generate.model'] = $this->app->share(function($app) { $generator = new ModelGenerator($app['files']).. the only remaining thing to do is try it out for real! cd to the root of your application.php will be created with the following boilerplate: 1 <?php 2 3 class Foo extends Eloquent { 4 5 } Go ahead and try out a few inversions of the command to prove that everything is functioning as expected. app/models/Foo.. 8 return new ModelGeneratorCommand($generator). 10 11 } That should do it! The tests pass. and run: 1 php artisan generate:model foo As our tests proved would happen.Chapter 13: Test-Driving Artisan Commands Exercise 1 200 // workbench/./Way/Generators/GeneratorsServiceProvider. Testing console output can be a bit tricky.Chapter 13: Test-Driving Artisan Commands Exercise 201 Summary In this chapter. Now. the Symfony team already considered this. we leveraged Symfony’s Console component to build a model generator with tests. luckily. and included a CommandTester class to ease the process considerably. even your custom Artisan commands will be test-driven! . but. Luckily. In addition to offering an easy way to create a private section of your web site (when a full login system isn’t necessary). as I was writing the demo API for this chapter. In fact. ⁴⁴http://tutsplus. in PHP. Here’s an example: 1 // app/routes. That said. often. As a result. Three Components to Writing an API in Laravel Coding an API from scratch is beyond the scope of this chapter (though I’ll cover the process considerably on Tuts+ Premium⁴⁴ in July. ['before' => 'auth. call a route. you’ll find that implementing a particular piece of functionality doesn’t require more than a few moments. and verify the response. function() { return 'Secret admin page. this would have required the better part of a day to implement. login a user (if applicable). it really couldn’t be simpler! APIs in Laravel One of the wonderful things about Laravel is that it was so clearly created (and updated) to remedy real-world problems and irritations. I found myself laughing at how easy it is. 2013).Chapter 14: Testing APIs Testing an API isn’t overly different from any other form of functional testing: seed the database.'. }]). those days are gone. Once you know what you’re doing.basic'.basic.php 2 3 4 5 6 Route::get('admin'. Authentication One of Laravel 4’s excellent new features is support for basic HTTP authentication. 1. Triggering basic auth only requires that you reference a native filter: auth. I’d be remiss not to review the essential components. Building APIs is no different.com . HTTP-based authentication can also be a perfect choice for simple APIs. Even five years ago. how might we leverage this functionality for authenticating API users? Quite simply. And then the controller: . 'PhotosApiController'). So. use the beforeFilter method within the controller’s constructor. that’s all you need to password-protect a page or group! Laravel for the win! HTTP authentication To instead reference this filter from your controllers. in fact: create a resource. each route will be protected with basic authentication.203 Chapter 14: Testing APIs Believe it or not. and reference the filter before all applicable routes. like so: 1 2 3 4 5 6 class MyController { public function __construct() { $this->beforeFilter('auth.php 2 3 Route::resource('photos'. } } Now.basic'). The route: 1 // app/routes. function() { return Auth::onceBasic(). Pay close attention to the naming convention for this prefix. This technique is particularly effective for APIs. as this chapter isn’t dedicated to API design. you’ll likely want to instead opt for an API key and secret.basic'). 1 2 3 Route::group(['prefix' => 'api/v1']. we’re able to extend the API’s functionality in the future. For more significant APIs. 1 2 3 4 Route::filter('auth. Route Prefixing As this is an API.Chapter 14: Testing APIs 1 204 // app/controllers/PhotosApiController.once'. } } Alternatively. such as api/v1. }). it’s not overly secure. however.basic. function() { Route::resource('photos'. you could create another filter within app/filters. it’s a smart idea to future-proof it by nesting the associated routes. such as api/v1 or api/v2. to prevent Laravel from setting a user identifier cookie in the session. Tip: Basic authentication for an API is the easiest way of verifying a user. using a prefix. 'PhotosApiController').php 2 3 4 5 6 7 8 class PhotosApiController { public function __construct() { $this->beforeFilter('auth. 2. By adopting a consistent pattern. }). . while continuing to provide a consistent interface for those who are still dependent upon an older version of the API.php and reference that. let’s keep things simple and stick with basic HTTP authentication. Nonetheless. if you want to return a JSON-representation for a user with an id of 1.205 Chapter 14: Testing APIs Using a prefix for API calls allows for future extensibility. when a collection is returned from a controller method. Return JSON Though you could provide support for returning both XML and JSON from your API. you’ll likely want to provide additional feedback to the API-caller. you’d only need to write: 1 2 3 4 public function show($id) { return User::find($id). Here’s an example that fetches all photos for the authenticated user. . This means. convenient as this is. why bother? It’s 2013: stick with JSON-only. and returns a JSON response. 3. what kind of developer prefers XML over JSON when querying a web service? I haven’t met these fictional creatures. // returns JSON } However. Besides. use the json method on the Response class. To manually return a JSON response from a route. that data will be translated into JSON. By default. when building APIs. we were able to correctly respond to incorrect and valid credentials. then don’t forget to leverage the authentication filter that we reviewed in the previous section.se/ . with very little code. In effect. In the image below. } 7 8 9 10 11 12 13 14 } The easiest way to test a web service is with curl⁴⁵.haxx. ⁴⁵http://curl. use Auth::user()->id. this will work perfectly. if your web app instead limits a photo’s visibility to the user associated with the photo. notice how. 'photos' => Auth::user()->photos->toArray() ]. } 6 public function index() { return Response::json([ 'error' => false.basic'). } } Don’t forget: once a user has been authenticated. however. 'photos' => Auth::user()->photos->toArray() ]. 1 2 3 4 5 class PhotosApiController extends \BaseController { public function __construct() { $this->beforeFilter('auth. Assuming that all photos are publicly viewable.Chapter 14: Testing APIs 1 2 3 4 5 6 7 8 9 206 class PhotosApiController extends \BaseController { public function index() { return Response::json([ 'error' => false. 200). 200). to capture the id of the authenticated user. you may access their associated user object with Auth::user(). which should provide a performance boost (though I’ve seen mixed benchmarks). If you haven’t already. subsequently. . update app/config/testing/database. as you’ll find. override all production configuration files with the ones stored in app/config/testing. and. we’ll of course be hitting a database. testing an API is relatively straight-forward. Use a Database in Memory As these are functional tests.php to set a new default driver for testing. there are. a few best practices to consider. chances are high that you can opt for a Sqlite database in memory. Assuming that your API is mostly CRUD-based. Did You Know?: When unit testing. first ensure that you’ve created a test database. along with its respective configuration file. To instruct Laravel to use a DB in memory.207 Chapter 14: Testing APIs Use curl to test your API Testing APIs While. nonetheless. Laravel will automatically set the environment to testing. Enable Filters One pitfall that you’ll undoubtedly come across is when Laravel seemingly ignores route filters when unit testing. always activate the filters. when testing. With this modification. even though you may expect a test to fail authentication. This will construct all necessary tables. you could do Artisan::call('db:seed'). Pay close attention to the fact that. like so: 1 2 3 4 class PhotosApiTest extends TestCase { public function setUp() { parent::setUp().Chapter 14: Testing APIs 1 208 <?php // app/config/testing/database. 5 'connections' => array( 'sqlite' => array( 'driver' => 'sqlite'. Migrate the Database for Each Test As we plan to reference a database in memory. instead. it’s important to migrate and seed the database before each test. the seed() method is used to populate the tables with seed records (if applicable). Alternatively. 5 Artisan::call('migrate'). 'prefix' => ''. $this->seed(). 'database' => ':memory:'. when testing your API. .php 2 3 4 return array( 'default' => 'sqlite'. Secondly. 6 7 } 8 9 } The above code leverages the Artisan facade to call the migrate command (php artisan migrate). ) ) 6 7 8 9 10 11 12 13 ). PHPUnit will still return green (because the filter never fired)! Yikes! To compensate for this. Laravel will automatically set the environment to testing and reference the in-memory DB. a database in memory is specified.sqlite file. As a result. rather than setting the database key to a . before each test. $this->seed(). 5 6 } Set the Authenticated User Lastly. // Or: Auth::loginUsingId(1). 10 11 12 13 Artisan::call('migrate'). 8 9 Route::enableFilters(). Invalid Credentials will be returned. Let’s say that you want to set the currently logged in user to the one with an id of 1.Chapter 14: Testing APIs 1 2 3 209 public function setUp() { parent::setUp(). let’s review some examples! User Must Be Authenticated In our fictional photo storage app. a good first test to write is one that asserts that. There are two ways to do this: 1 2 3 $this->be(User::find(1)). we’ve decided that access to all photos should be limited to the user with whom they’re associated. if a particular route is requested. As such. specify an authenticated user before each test. Test Examples With a few guidelines in place. 1 // app/tests/api/PhotoApiTest. when testing APIs that require authentication. 4 Route::enableFilters(). . and credentials are not provided.php 2 3 class PhotoApiTest extends TestCase { 4 5 6 7 public function setUp() { parent::setUp(). Chapter 14: Testing APIs 210 Auth::loginUsingId(1); 14 } 15 16 public function testMustBeAuthenticated() { // Most tests will assume a logged in user // But not this one. Auth::logout(); 17 18 19 20 21 22 $response = $this->call('GET', 'api/v1/photos'); 23 24 $this->assertEquals('Invalid credentials.', $response->getContent()); 25 } 26 27 } Pretty simple, ay? Check For Error Please note that, for all remaining tests, the setUp() method will be assumed. Let’s write a test that verifies whether an error property exists on the returned object. This should provide some added feedback as to whether the request was successful or not. 1 2 3 4 public function testProvidesErrorFeedback() { $response = $this->call('GET', 'api/v1/photos'); $data = json_decode($response->getContent()); 5 $this->assertEquals(false, $data->error); 6 7 } Fetch All Photos For the Authenticated User Next, we need a test that fetches all photos for an authenticated user, and verifies whether the correct JSON is returned. Chapter 14: Testing APIs 1 2 3 4 5 211 public function testFetchesAllPhotosForUser() { $response = $this->call('GET', 'api/v1/photos'); $content = $response->getContent(); $data = json_decode($content); 6 // Did we receive valid JSON? $this->assertJson($content); 7 8 9 // Decoded JSON should offer a photos array $this->assertInternalType('array', $data->photos); 10 11 12 } When testing an API, one of the most important assertions you can make is one that verifies whether real JSON was returned. Luckily, as of PHPUnit v3.7, an assertJson method is available. Refactoring Already, though, I can see that some cleanup is in order. If the tests return green, I’m free to refactor. Let’s break this into pieces. Here’s a new test that verifies whether valid JSON was returned from the request. 1 2 3 public function testReturnsValidJson() { $response = $this->call('GET', 'api/v1/photos'); 4 $this->assertJson($response->getContent()); 5 6 } Next, one for verifying that the photos array is available. 1 2 3 4 public function testFetchesPhotos() { $response = $this->call('GET', 'api/v1/photos'); $data = json_decode($response->getContent()); 5 $this->assertInternalType('array', $data->photos); 6 7 } Or, if you’re feeling particularly rambunctious, you could write an assertion that decodes a JSON string, and validates whether a particular key exists on it. You crazy kid. Chapter 14: Testing APIs 1 2 3 212 public function testFetchesPhotos() { $response = $this->call('GET', 'api/v1/photos'); 4 $this->assertJsonStringHasKey('photos', $response->getContent()); 5 6 } 7 8 9 10 protected function assertJsonStringHasKey($key, $json) { $data = json_decode($json); 11 $this->assertInternalType('array', $data->$key); 12 13 } Updating a Photo Sure, fetching a list of your photos is great, but what about creating or updating? Making such a request with curl might take the form of: 1 2 curl -X PATCH --user [email protected]:1234 -d "caption='Changed my photo'" loca\ lhost:8000/api/v1/photos/1 When working with curl, -X refers to the request type (PUT, DELETE, etc.), and -d is for data that should be sent through. The following example will set a factory for a Photo and save it to the DB, and then call the necessary API route to update the record. 1 2 3 4 5 6 7 8 public function testUpdatesExistingPhoto() { // Poor man's factory $photo = new Photo; $photo->caption = 'Some Photo'; $photo->path = 'foo.jpg'; $photo->user_id = 1; $photo->save(); 9 10 $updatedPhotoFields = ['caption' => 'Updated Photo Caption']; 11 12 $response = $this->call('PATCH', 'api/v1/photos/1', $updatedPhotoFields); 13 14 $data = json_decode($response->getContent()); Chapter 14: Testing APIs 213 15 $this->assertEquals('Photo has been updated', $data->message); $this->assertEquals('Updated Photo Caption', Photo::find(1)->caption); 16 17 18 } Notice how we’re passing the parameters as the third argument to the call method. From the controller, you can capture these values as you normally would when responding to a form’s submission. 1 2 $fields = Request::get(); // Or Input::get() if you prefer $caption = Request::get('caption'); Once the route has been triggered, we merely inspect the returned JSON and ensure that the message is equal to “Photo has been updated.” Factories In the previous snippet, we used what I’d refer to as a poor man’s factory. Let’s clean that up using my Factory class that we reviewed in a previous chapter. You can pull it into your project by either installing Laravel 4 Generators⁴⁶ (which includes the package) or Laravel Test Helpers⁴⁷. Personally, I use the custom generators in every project, so that’s what we’ll use here. Definition: In the simplest possible terms, a factory manages the creation of objects with dummy data. To replace this big chunk of code: 1 2 3 4 5 6 // Poor man's factory $photo = new Photo; $photo->caption = 'Some Photo'; $photo->path = 'foo.jpg'; $photo->user_id = 1; $photo->save(); We only need to write: ⁴⁶https://github.com/JeffreyWay/Laravel-4-Generators ⁴⁷https://github.com/JeffreyWay/Laravel-Test-Helpers Chapter 14: Testing APIs 1 214 Way\Tests\Factory::create('Photo'); Or, better yet, use the class at the top of the file. 1 2 3 use Way\Tests\Factory; // ... Factory::create('Photo'); This will achieve the exact same result as what we had before, except, this time, the class will fake the values for each field. To override any fields, pass an array as the second argument to the create method, like so: 1 Factory::create('Photo', ['caption' => 'Some caption']); With this modification, the testUpdatesExistingPhoto spec may be updated to: 1 2 3 4 public function testUpdatesExistingPhoto() { Factory::create('Photo'); $updatedPhotoFields = ['caption' => 'Updated Photo Caption']; 5 $response = $this->call('PUT', 'api/v1/photos/1', $updatedPhotoFields); $data = json_decode($response->getContent()); 6 7 8 $this->assertEquals('Photo has been updated', $data->message); $this->assertEquals('Updated Photo Caption', Photo::find(1)->caption); 9 10 11 } Specifying Options Basic API design dictates that options should be declared within the query string. Try not to use verbs within your URIs. As such: 1 api/v1/photos?color=green …is clearly a smarter choice than: 1 api/v1/getGreenPhotos Hopefully, that goes without saying. Let’s write a test that specifies a limit for the number of photos that should be returned. ['user_id' => '1']). 12 13 14 } To implement this type of functionality. 6 7 8 9 10 } 11 12 13 14 15 protected function applyOptions($photos) { if ($limit = Request::get('limit')) $photos->take($limit). ['user_id' => '1']). 17 18 } Summary One thing worth noting is that we exclusively relied upon functional (or some might refer to this as integration) testing. Factory::create('Photo'. we might write: 1 2 3 4 public function index() { $photos = Auth::user()->photos(). 'api/v1/photos?limit=1'). Factory::create('Photo'. 200). 'photos' => $photos->get()->toArray() ]. 5 return Response::json([ 'error' => false. $data->photos). you’d likely want to create unit tests for the PhotosApiController class as well. $data = json_decode($response->getContent()). 16 return $photos. not 2 $this->assertCount(1. In a real-world project. providing the best of both worlds.Chapter 14: Testing APIs 1 2 3 4 5 6 215 public function testCanSetALimit() { // Give a new user two photos Factory::create('User'). 7 // Declare a limit of 1 $response = $this->call('GET'. 8 9 10 11 // Verify that the photos array count is 1. . $photos = $this->applyOptions($photos). Chapter 14: Testing APIs 216 In this chapter. and then assert (AAA). we used a Sqlite database in memory and Factories to test a fictional photos API. Really. the basic process should be quite familiar to you at this point. . act. As I noted at the beginning. it’s no different than any other type of testing: arrange. Its configuration is kept short. which builds upon popular components.com⁴⁸ An Amuse Bouche Before we dig further into how Codeception allows us to better test our applications. is the solution. the more likely it is that the test simply won’t be written. Wouldn’t it be amazing if we could interact with our web apps on a high level using a simple.” . As a hello world example. The only requirements are a basic knowledge of PHP. we made use of a couple Symfony components to inspect the DOM (BrowserKit and DomCrawler). when /login is requested. the text. As they say. “Login”. let’s say we want to ensure that. readable interface that even non-developers could parse? Codeception. like PHPUnit and Mink.codeception. codeception. here’s a quick teaser of the syntax. and managers can use Codeception. PHPUnit does not offer too many helpers for writing functional tests.com/ . we could write: ⁴⁸http://codeception. Using Codeception. however. is displayed. the process was. still too cumbersome. the more difficult a test is to write. PHP developers. in this author’s opinion.com “Codeception is kept as simple as possible for any kind of user. QAs.Chapter 15: Acceptance Testing With Codeception By itself. In a previous chapter. and theory of automated testing. all common issues are already solved. These need to be greased lightning fast. For instance: • • • • • • • • Get in the car Put key into ignition and start car Turn on the time circuits Set date to November 5th. we’ve mostly focused on testing in isolation. Notice how readable that is! Codeception places a significant emphasis on readability. so that you can easily run the full suite each time a file is saved. Surprised that this percentage is so high? Well think about it: if we were building a time machine. $I->amOnPage('/login'). This is because they’re meant to mimic the feature requests that your client might give you. Codeception can make assertions against what’s stored in the database. it can auto-generate plain-text scenarios. 1 codecept generate:scenarios But this is just the tip of the iceberg. There’s a reason for this: roughly eighty percent of your tests should be unit tests. Strip away as much clutter as possible. It even goes so far as to recommend that you use an $I variable when instantiating your scenarios.Chapter 15: Acceptance Testing With Codeception 1 2 3 218 $I = new WebGuy($scenario). 1955 Ensure that flux capacitor is fluxing Step on the gas Accelerate to 88mph Assert that electric sparks fly . and get right down to what you need to test. Even better. $I->see('Login'). click links. if needed. Codeception tweaks the formula just a bit to remove any potential duplication. we’d definitely want to run some tests for ensuring that the car works correctly for the customer (Marty). and much more…all using the same basic syntax demonstrated above. perform AJAX requests. For example: 1 2 When I am on the login page I expect to see the text Login While tools like Cucumber emphasize plain text scenarios. submit forms. Testing Refresher So far in this book. the fuel injection manifold (I know that one from Back to the Future 3). though incredibly important. the car doesn’t move when you step on the gas? In the real-world. 1 2 3 As a user To more easily consume Tuts+ Premium content I want to download each course as a zip file Notice how these stories are quite generalized . which ensure that each component of the car . They don’t delve into the specifics (when I view this page. and acceptance tests. From way up there. they provide incredibly accurate and real-world results. “I’ve implemented the feature you requested. Think of it as a newspaper’s two sentence description of some new movie that’s out in theaters.Chapter 15: Acceptance Testing With Codeception 219 This is an acceptance test! Notice how it flexes the entire system (or Delorean). If these were the only tests you wrote. I want to see a download button that links to the course’s zip file). the customer (whoever that might be) will typically describe the functionality that they require.functions as expected. slamming your head into the steering wheel won’t magically fix things. with the remainder dedicated to integration. In effect.hence. versus functional and acceptance tests. If it isn’t acceptable. also has the unavoidable side effect of being a high level test. query real web services. we can deduce that.almost like a roadmap that charts out where you currently are. lock on to the word. How could you immediately detect what went wrong? It could be a loose wire. and should be acceptable to the customer. gas might not be getting to the engine thing (I know nothing about cars . This is probably a good test to write! But what happens if. 80% unit. once this test passes. plan on the number of unit test lines being the same as your production code. but. then. at some point.the starter. the radio . and will run in an environment that is as close to production as possible. which can be executed in a matter of seconds. The key is to find the perfect ratio of unit tests. accept. who knows? So it seems that testing from a bird-eye level. But. As a basic rule of thumb (remember that rules may be broken). Acceptance Testing Acceptance tests execute code from the outside in. even though this test is essential. From this. functional. a user story should be condensed. We refer to these as user stories. the feature has been fully implemented. as a side effect. have no choice but to be orders of magnitude slower than your unit tests. could be a faulty pedal. and where you’re going. etc. Though it doesn’t always play out this way in real life (not your fault). presumably. “engine thing”). we also require lower level tests. Do you accept that it works as expected?” . you can’t see a thing. for some reason. Waiting even ten seconds for tests to run is out of the question. There’s a time and place to fetch that information from the customer. When building new features. This means that they will hit the database. To easily remember what acceptance testing refers to. and the spec wasn’t written correctly. to start. they would break the incremental TDD cycle. wires became crossed. I’ve yet to meet a team that functioned in this way. themselves. 1 wget http://codeception. so that it can be referenced globally.Chapter 15: Acceptance Testing With Codeception 220 Tools like Cucumber were specifically written to give managers and quality assurance folks the ability to write these high-level tests for you. don’t forget to ensure that it’s executable. 1 chmod +x /usr/local/bin/codecept . there are a couple different ways to download it. Tools like Cucumber were specifically written to give managers and quality assurance folks the ability to write these high-level tests themselves. you’ll probably want to move it somewhere permanent.phar Next. we first need to install Codeception. Global Installation One way to install Codeception is to manually fetch its archive file. Installation As with any new tool. 95% of the time. but. based upon prior discussions with the clients. Like Composer. It’s a cute end-goal. before we can dig in. you’ll find yourself (or someone else on your team) writing the acceptance test.phar /usr/local/bin/codecept Finally.com/codecept. 1 mv codecept. *" }. 1 composer update And that’s it! The codecept executable will be located at vendor/bin/codecept.1. we update Composer to download the new package.Chapter 15: Acceptance Testing With Codeception 221 That should do it! Test it out by creating a new tab and running codecept. like so: ⁴⁹https://packagist.6. either add vendor/bin to your system path. Next. Installing Codeception globally Local Installation A second way that we can pull in Codeception is through Composer⁴⁹. We first update the composer. you’ll see a list of all available commands.json file to reference the Codeception package (I’ll be using version 1.11 in this chapter). If installed correctly. of course.6. To view the list of commands (as we did before). "require-dev": { "codeception/codeception": "1. 1 2 3 4 5 6 "require": { "laravel/framework": "4. or reference its full path.1" }.0.org/packages/codeception/codeception . . a suite of files will be added to the app/tests directory. 1 codecept bootstrap This command will dynamically generate a number of files within a tests/ directory. we want these files to be placed within app/tests.Chapter 15: Acceptance Testing With Codeception 1 222 vendor/bin/codecept Success! Codeception’s list of commands Now that we’ve successfully installed it on our system. Bootstrapping Codeception is a full-stack testing framework and needs to be initialized for each new project. 1 codecept bootstrap app Upon running this command. when bootstrapping. For languageagnostic projects. however. As such. let’s do some testing. this will do just fine. for Laravel applications. be sure to include the path argument to explicitly set a custom install path. suite. and acceptance. When writing unit tests. . Tests may be created in two ways: manually or generated. make note of how tests are divided into unit. this is a popular convention that provides an easy way to execute a specific suite of tests. 1 2 3 4 5 6 7 8 class_name: WebGuy modules: enabled: . functional. As we reviewed earlier in this book. Codeception will provide a couple lines of boilerplate code to get you started. for Codeception to run properly. and must provide the URL for your app within app/tests/acceptance.WebHelper config: PhpBrowser: url: 'http://localhost:8000' While you’re here. The only difference is that. We’ll review this more shortly. Generate a Test Let’s write one dummy test to observe the basic cycle. and then we’ll move on to real-world tests. Either method will do just fine. make a mental note that each suite’s functionality may be extended with modules.Chapter 15: Acceptance Testing With Codeception 223 After running codecept bootstrap app Specifically.yml.PhpBrowser . you likely don’t want to wait for the slow acceptance tests to run as well! Configuring Acceptance Tests Because acceptance tests work from the outside in. when using the latter approach. you need to be running a server (php artisan serve). app/tests/acceptance/WelcomeCept.yml codeception. Let’s make a couple of changes to remove the need to specify the path to the configuration file. $I->amOnPage('/'). a configuration exception will be thrown.php. $I->wantTo('Check the home page for a welcome message'). update this file. replace all references to tests/ with app/tests. $I->see('Welcome'). and.yml Next. 1 codecept generate:cept acceptance Welcome -c app This can get cumbersome. This is because we set a custom path to the install directory. and append: 1 <?php 2 3 4 5 6 $I = new WebGuy($scenario).yml to the root of your project. Generator Approach From the command line. 1 mv app/codeception. noting that Codeception could not locate the codeception.Chapter 15: Acceptance Testing With Codeception 224 Manual Approach Create a new file. To compensate. within the paths object. run: 1 codecept generate:cept acceptance Welcome If this command is run from the root of your Laravel application. like so: . though.yml file. use the -c option to set the path to the configuration file as well. That’s a lot to write for the sole purpose of generating a single file with two lines of boilerplate code. Move app/codeception. Decoding the Command The generate command consists of a few components. ensure that the acceptance/WelcomeCept. We’ll review both of these in the coming chapters. we can simply write: 1 codecept generate:cept acceptance Welcome This will generate app/tests/acceptance/WelcomeCept. • The name of the command to run (generate:cept). acceptance) • The name of the test. functional.Chapter 15: Acceptance Testing With Codeception 1 2 3 4 5 225 paths: tests: app/tests log: app/tests/_log data: app/tests/_data helpers: app/tests/_helpers That should do it! Now. $I->wantTo('perform actions and see result'). It’s up to you to decide which method you most prefer. Codeception supports two different formats: Cept and Cest. • The name of the suite to add the file to (unit.php with: 1 <?php 2 3 4 $I = new WebGuy($scenario). Decoding the command Both the manual and generate approach will achieve the same end result. Writing the First Test Depending on which method you chose for creating a new acceptance test.php file contains: . php As a best practice. $I->see('Welcome'). Though it isn’t required. $I->wantTo('Check the home page for a welcome message'). 1 codecept run Running tests . it does help to provide a better description for the test. The only method that may require further explanation is wantTo(). opt for readability and always include this method call. when viewing the results. For example: With: 1 1) Couldn't check the home page for a welcome message in WelcomeCept. Running All Tests The codecept run command may be used to trigger all tests in your application.226 Chapter 15: Acceptance Testing With Codeception 1 <?php 2 3 4 5 6 $I = new WebGuy($scenario). $I->amOnPage('/'). they’re self explanatory.$I->see() rather than $I->shouldSee().php Without: 1 1) WelcomeCept. The wonderful thing about scenario-based tests in Codeception is that the methods above don’t require much explanation. Notice how all tests are written in present tense . And we’re green! 1 2 Suite acceptance started Trying to check the home page for a welcome message (WelcomeCept. specify the name of the suite to run: codecept run acceptance. which will specify the name of the single test to run.Ok 3 4 5 Suite functional started Suite unit started 6 7 8 Time: 0 seconds. we only need to create a route that returns the string. this is a bit misleading. For further filtering. Welcome. step one will seemingly pass. This will. all test suites will be triggered when running codecept run. To make the test pass. notice how Codeception will highlight the scenario step that it failed on. . To override this. Even if no / route exists. 2 assertions) It’s so easy! Tip: By default. a second argument may be passed. only trigger the tests within app/tests/acceptance. in effect.php 2 3 4 5 6 Route::get('/'. 1 // app/routes. Memory: 9.Chapter 15: Acceptance Testing With Codeception 227 In the image above. you may use the seeResponseCodeIs(200) method. Don’t forget that you can view the documentation for any command by preceding its name with help: codecept help run. To ensure that the response code is correct.php) . function() { return 'Welcome'. This can be incredibly helpful when determining exactly what went wrong. Technically. as the first argument to the run command. }).50Mb OK (1 test. Codeception failed to find Welcome in the response. In this case. . we reviewed the absolute basics of testing with Codeception. we’ll continue using acceptance testing to build a relatively simple login form with authentication. there is much more to cover. clearly. With the basic syntax under your fingers. in the next Exercise chapter.Chapter 15: Acceptance Testing With Codeception 228 Summary In this chapter. but. This can include everything from authentication. That’s what all the other styles of testing are for. as with anything. As you work along. you’ll begin to realize where acceptance tests fall short (speed and feedback). there’s a variety of reasons why we don’t do this. some integration tests for verifying the integration of two or more classes. In other words. Why not write a few tests that describe the completed feature. unit tests to ensure that each component works correctly. In this chapter. it’s important to limit them to regions of your application that require them. and then code away until those pass? Well. etc. which allows each path to be contained more traditionally within a class method. to billing. you’ll find yourself writing tests that span the full spectrum of the application: some acceptance tests to define the end goal for the customer. when coding. You see.and acceptance testing in general . we’ll exclusively use acceptance tests to build a login feature for a client. But. . verifying every possible path is not something that should be done on the acceptance testing level. The Feature Let’s imagine that our fictional customer would like to have a new private (password-protected) administration area for their website. We’ll use the Cest format in this chapter. to implementing a new feature for your web app. the best way to learn why is through experience. hopefully. I asked this very question at one point in my learning.Chapter 16: Authentication With Codeception Exercise Because features .come with a relatively significant performance cost. The user story might be: 1 2 3 In order to perform administrative tasks As the site owner I want to login to a password-protected private area Translating the Feature for Codeception The first step is to create a new acceptance test. Perhaps you’re still wondering why anything but acceptance tests are necessary. This is why these types of tests are referred to as executing from the outside-in: they exercise the entire application. Think of the code above less as performing a specific action.Chapter 16: Authentication With Codeception Exercise 1 230 codecept generate:cest acceptance Login Using Codeception. 1 <?php // app/tests/acceptance/LoginCest. 17 18 } 19 20 21 } Again. as it should be protected. . 'h1'). $I->click('Login'). $I->lookForwardTo('perform administrative tasks'). $I->seeCurrentUrlEquals('/login'). 13 14 15 16 $I->seeCurrentUrlEquals('/admin'). 1. $I->wantTo('login to a password-protected area'). $I->wantTo('login to a password-protected area'). $I->lookForwardTo('perform administrative tasks'). At this point. we can write code to interact with the web application in the same way that a human being might. 10 11 12 $I->fillField('email'. rather than one or two components (which would be integration testing). '1324').php 2 3 class LoginCest { 4 public function logsInUserWithProperCredentials(WebGuy $I) { $I->am('Site Owner').com'). $I->fillField('password'. Visit the admin page. 'jeffrey@envato. the above user story may be translated to: 1 2 3 $I->am('Site Owner'). at a high level. but expect to be redirected to the login page. notice how. even if you weren’t a developer. $I->see('Admin Area'. Below is our first test for logging in with proper credentials. 5 6 7 8 9 $I->amOnPage('/admin'). you could probably figure out what this code does. and more defining the story background. of course. Admin Area Running the tests at this point will. I am on page "/admin" 2. /login . So that I perform administrative tasks 1. Expect to be redirected to ‘/admin’. /admin . I see current url equals "/login" <-. .RED 3.Chapter 16: Authentication With Codeception Exercise 231 2.loginWithProperCreden\ tials Guy couldn't see current url equals "/login": Failed asserting that two strings a\ re equal. --. and click the Login button 3.php.Should be auth protected 2. and see the text. As a Site Owner Looks like we have our first step! Register Routes According to the tests. we require two routes: 1. Fill out the username and password.Expected +++ Actual @@ @@ -'/login' +'/admin' 10 11 12 13 14 15 Scenario Steps: 4.For creating a new user session Per usual. lead to failure. these can be added to app/routes. 1 2 3 4 5 6 7 8 9 1) Couldn't login to a password-protected area in LoginCest. Chapter 16: Authentication With Codeception Exercise 1 232 <?php // app/routes."jeffrey@envato. we’ll receive the next step: 1 2 3 4 1) Couldn't login to a password-protected area in LoginCest. So that I perform administrative tasks 1. 8 9 10 11 12 Route::get('login'. I see current url equals "/login" 3. function() { // Temporary return '<h1>Admin Area</h1>'. As a Site Owner Clearly. ['before' => 'auth'. }]).com": Field matching id|name|labe\ l|value or css or xpath selector does not exist 5 6 7 8 9 10 11 Scenario Steps: 5. .php 2 3 4 5 6 7 Route::get('admin'. Codeception can’t fill in form fields that don’t exist.RED 4. As this project doesn’t have a master page. Building the Form The next step is to add the login form. If we run the tests again.loginWithProperCreden\ tials Guy couldn't fill field "email". I fill field "email". function() { return View::make('sessions.create'). })."[email protected]" <-. let’s keep things simple and embed the full HTML here. I am on page "/admin" 2. 'Password') }} {{ Form::password('password') }} </div> 22 23 24 25 26 27 28 <div> {{ Form::submit('Login') }} </div> {{ Form::close() }} </body> </html> Should we run the tests now.app/views/sessions/create.php --> <!doctype html> <html> <head> <meta charset="utf-8"> <title>Login</title> </head> 8 9 10 <body> <h1>Login</h1> 11 12 13 14 15 16 {{ Form::open() }} <div> {{ Form::label('email'. we’ll see that. So that I perform administrative tasks .com" 4."1324" 5. 1 2 3 4 5 6 7 8 Scenario Steps: 8. 'Email') }} {{ Form::text('email') }} </div> 17 18 19 20 21 <div> {{ Form::label('password'. I am on page "/admin" 2."jeffrey@envato. I click "Login" 6. when the form was submitted. Codeception expected to be redirected to /admin. but was not.Chapter 16: Authentication With Codeception Exercise 1 2 3 4 5 6 7 233 <!-. I see current url equals "/login" 3.blade.RED 7. I fill field "email". I see current url equals "/admin" <-. I fill field "password". 1 // app/routes. 'SessionsController').php 2 3 Route::resource('sessions'.Chapter 16: Authentication With Codeception Exercise 234 Resources To make this test pass. \ password:string" This takes care of the migration and schema. 1 php artisan controller:make SessionsController We also need to update the routes file to register these routes. but we also need a test record within this table. we first needs a users table within our test database.com/JeffreyWay/Laravel-4-Generators . and specify that the form should POST to the store method of SessionsController. we first need a new resourceful controller for managing user sessions. My Laravel 4 Generators⁵⁰ tool is the quickest way to tackle tasks like this: 1 2 php artisan generate:migration create_users_table --fields="email:string:unique.store']) }} Authenticating the User In order to write authentication code. excluding the test record that I’ve already inserted. 'route' => 'sessions. 1 php artisan generate:seed Users This command will generate the following boilerplate. 1 {{ Form::open(['method' => 'post'. we can update the view. ⁵⁰https://github. Now. Laravel offers seed files for this very purpose. Chapter 16: Authentication With Codeception Exercise 1 235 <?php // app/database/seeds/UserTableSeeder.php 2 3 class UsersTableSeeder extends Seeder { 4 public function run() { $user = new User; $user->email = '[email protected]'; $user->password = Hash::make('1234'); $user->save(); } 5 6 7 8 9 10 11 12 13 } Adding a Test Database Before moving forward, ensure that you have a test database setup. This can be accomplished by creating app/config/testing/database.php. This will have the effect of overriding the default DB settings. 1 <?php // app/config/testing/database.php 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 return array( 'connections' => array( 'mysql' => array( 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'my-test-db', 'username' => 'root', 'password' => '1234', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ) ) ); Be sure to replace the credentials above with your own. Also, Codeception needs to know these details as well. They can be added to the master codeception.yml file that should be in the root of your project. Chapter 16: Authentication With Codeception Exercise 1 2 3 236 modules: config: Db: dsn: 'mysql:host=localhost;dbname=TEST_DB_NAME' user: 'USERNAME' password: 'PASSWORD' dump: app/tests/_data/dump.sql 4 5 6 7 But, that’s it; you now have a test database ready to go! Let’s migrate and seed this new users table. 1 2 3 php artisan migrate --seed --env="testing" Migrated: 2013_05_19_151342_create_users_table Database seeded! At this point, we can authenticate the user within SessionsController. 1 2 3 4 5 6 7 // app/controllers/SessionsController public function store() { $creds = [ 'email' => Input::get('email'), 'password' => Input::get('password') ]; 8 if (Auth::attempt($creds)) return Redirect::to('admin'); 9 10 } Run the tests again, and we get green! Invalid Credentials I’d like to write one more test, though. Let’s make sure that, if incorrect credentials are specified, then the login page is reloaded, along with an Invalid Credentials error message. Chapter 16: Authentication With Codeception Exercise 1 237 // app/tests/acceptance/LoginCest.php 2 3 4 5 6 public function loginWithInvalidCredentials(WebGuy $I) { $I->amOnPage('/login'); $I->click('Login'); 7 $I->seeCurrentUrlEquals('/login'); $I->see('Invalid Credentials', '.flash'); 8 9 10 } Run the tests: 1 2 3 4 5 6 7 8 1) LoginCest.loginWithInvalidCredentials Guy couldn't see current url equals "/login": Failed asserting that two strings a\ re equal. --- Expected +++ Actual @@ @@ -'/login' +'/sessions' 9 10 11 12 13 Scenario Steps: 3. I see current url equals "/login" <-- RED 2. I click "Login" 1. I am on page "/login" This is one of those situations where we don’t know exactly what the next step is. That’s the downside to writing acceptance tests. We’ve proven that this piece of functionality doesn’t work, but we don’t have enough tests to determine precisely what to do next. Now, if we had also written some unit and functional tests, this would never be the case. Nonetheless, we’ll continue on. Let’s update the controller’s store method to redirect back to the login page if authentication fails. Chapter 16: Authentication With Codeception Exercise 1 238 // app/controllers/SessionsController.php 2 3 4 5 6 7 8 public function store() { $creds = [ 'email' => Input::get('email'), 'password' => Input::get('password') ]; 9 if (Auth::attempt($creds)) return Redirect::to('admin'); 10 11 return Redirect::to('login')->withInput(); 12 13 } The final step is to ensure that the Invalid Credentials message displays, as the latest running of the tests show: 1 2 3 4 5 Scenario Steps: 4. I see "Invalid Credentials",".flash" <-- RED 3. I see current url equals "/login" 2. I click "Login" 1. I am on page "/login" In Laravel, we can flash messages when redirecting by passing a session key and value pair to the with method. 1 2 3 return Redirect::to('login') ->withInput() ->with('message', 'Invalid Credentials'); To capture this value from the view, we only need to verify whether the session object has a message key, and, if so, display it within a <div>. 1 2 3 4 5 @if (Session::has('message')) <div class="flash"> {{ Session::get('message') }} </div> @endif And that should do it! If working along, manually check your work in the browser to prove that the login form functions as expected. Chapter 16: Authentication With Codeception Exercise 239 Tip: Once you embrace a consistent TDD cycle, you’ll find that, most of the time, your browser will remain closed. Summary Hopefully, if you worked along with this chapter, you found that, though helpful, we require more fine-grained tests to handle the situations when a scenario step fails without providing enough details. In the following chapter, we’ll toy around with functional testing in Codeception, which offers mostly the same API, while offering a few bonuses. Rather than noting that a scenario step failed. This can provide a performance boost.NET application).Chapter 17: Functional Testing in Codeception In Codeception. functional tests will display the exception message directly in the console. Codeception provides a Laravel4 module that can hook into our favorite framework. The Laravel4 module may be enabled by editing functional. TestHelper. Luckily. By default. While acceptance tests are framework-agnostic (meaning you could even use Codeception to write tests for a . and password with your credentials. this file will include the Filesystem and TestHelper modules.and provide your database credentials within the DB configuration block. which we discussed in the previous chapter. There are only two core differences: • Functional tests don’t require a server to execute.yml . Laravel4. First. edit the global configuration file . the same is not true for your functional tests. Instead.suite. Db] The DB Module The DB module requires a bit of setup. Let’s extend it to support both Laravel4 and Db. The Laravel4 Module Codeception is extensible in the form of modules. . requests and responses will be emulated.yml. the process of writing functional tests is nearly identical to acceptance testing. username. Don’t forget to update the DSN.codeception. 1 2 3 class_name: TestGuy modules: enabled: [Filesystem. • Functional tests provide better output for debugging. Codeception requires a raw SQL dump. 0 methods added Registering a User In the previous chapter. . based upon the modules that you’ve included.dbname=DB_NAME' user: 'USERNAME' password: 'PASSWORD' dump: app/tests/_data/dump. we can dump our database to this location in a single command: 1 2 mysqldump --opt --user="USERNAME" --password="PASSWORD" DBNAME > app/tests/_data/\ dump. 39 methods added /Users/Jeffrey/Desktop/codecept-chapter/app/tests/functional/TestGuy.this time. we wrote an acceptance test for logging a user in. This way. we can ensure that each test is working with the same data set. To populate your database. Updating TestGuy The final step is to re-build the TestGuy class. for the functional suite. re-run the build command. Using the previously mentioned executable. You can either tackle this directly from a GUI. 1 2 3 4 5 6 7 codecept build /Users/Jeffrey/Desktop/codecept-chapter/app/tests/acceptance/WebGuy. The reason why is because files.sql. Any time that you add or remove a module from a suite.sql The DB module’s core responsibility is to clean up the database after each test. using functional testing. and WebGuy are dynamically generated. or most systems should have a mysqldump executable available from the command line. 52 methods added /Users/Jeffrey/Desktop/codecept-chapter/app/tests/unit/CodeGuy. filling out the form. like TestGuy. and then verifying whether the new user was saved to the database.Chapter 17: Functional Testing in Codeception 1 2 3 4 5 6 7 241 modules: config: Db: dsn: 'mysql:host=localhost. let’s write a test for registering a new user. This SQL dump should be placed within app/tests/_data/dump. This test should walk through the process of visiting the /register page. As always.php generate\ d successfully.php generated\ successfully.php generated succ\ essfully. This time. replace values in all caps with your own credentials. we begin by generating a new test . CodeGuy.sql As always. like Sequel Pro (via an “Export” command). we can peek into the database. seeInDatabase? This references some of the sugar that the Db module provided. let’s write the spec for registering a user. along with the stack trace. $I->wantTo('register for a new account').php Symfony\Component\HttpKernel\Exception\NotFoundHttpException: It’s squawking because the /register route doesn’t exist. 'h1'). $I->seeInDatabase('users'. 1 <?php // app/tests/functional/RegistrationCept.com']). Did you notice that last method. This time. the database will be refreshed. $I->fillField('Password:'. for each test.flash').Chapter 17: Functional Testing in Codeception 1 242 codecept generate:cept functional Registration Next. $I->click('Register Now'). $I->see('You may now sign in!'. and ensure that a new record was. Don’t forget that. in fact.php 2 3 4 5 $I = new TestGuy($scenario). 'joe@sample. 1 2 1) Couldn't register for a new account in RegistrationCept. added to the users table. $I->fillField('Email:'. if we run the tests. $I->lookForwardTo('be a member'). With a single line of code. . 12 13 14 15 $I->seeCurrentUrlEquals('/login'). we’ll see that an exception was thrown. '1234'). $I->see('Register'. 6 7 8 9 10 11 $I->amOnPage('/register').com'). rather than Codeception failing on a scenario step without providing any feedback. ['email' => 'joe@sample. '. function() { return View::make('register'). 'Email:') }} {{ Form::text('email') }} 14 15 16 {{ Form::label('password'. .php 2 3 4 5 6 Route::get('register'. Next. Forgive me for ignoring validation. 'Password:') }} {{ Form::text('password') }} 17 18 19 20 21 {{ Form::submit('Register Now') }} {{ Form::close() }} </body> </html> Great! According to the tests. let’s move ahead and create the registration form. }). 1 2 3 4 5 6 <!doctype html> <html> <head> <meta charset=utf-8> <title>Register</title> </head> 7 8 9 <body> <h1>Register</h1> 10 11 12 13 {{ Form::open() }} {{ Form::label('email'.Chapter 17: Functional Testing in Codeception 1 243 // app/routes. let’s keep things barebones for the sake of focusing as much as possible on Codeception. the only remaining thing to do is respond to the POST request. and then create the user. and should then be done! 1 2 <body> <h1>Register</h1> 3 4 5 6 7 8 @if (Session::has('message')) <div class="flash"> {{ Session::get('message') }} </div> @endif 9 10 11 12 {{ Form::open() }} {{ Form::label('email'. $user->password = Input::get('password'). . 10 11 12 }). $user->email = Input::get('email').Chapter 17: Functional Testing in Codeception 1 244 // app/routes. 'Password:') }} {{ Form::text('password') }} 16 17 18 19 {{ Form::submit('Register Now') }} {{ Form::close() }} </body> Back to green. we’ll display the flash message to the user. Did you notice that we implemented this functionality without ever touching a browser? And the best part is that. 'Email:') }} {{ Form::text('email') }} 13 14 15 {{ Form::label('password'. 9 return Redirect::to('login') ->with('message'.php 2 3 4 5 6 7 8 Route::post('register'. for the life of this application. Finally. function() { $user = new User. $user->save(). we will have a test that verifies whether or not the registration process works as we expect it to. 'You may now sign in!'). and then write the necessary code (while still leveraging traditional unit tests) to make it pass. It’s really quite simple: define how the user wishes to interact with their application using a readable DSL.245 Chapter 17: Functional Testing in Codeception Back to green Summary The fact that this chapter is so short is a testament to how easy Codeception is to use. . Tools that are built well should not require hundreds of pages of documentation. To repeat. you may already be practicing continuous integration (mostly). Even ten years ago. like Git⁵³. Upon receipt. This cycle can help to prevent compatibility issues. 5. continuous integration is the process of frequently (even multiple times a day) merging (or integrating) your local source revisions in an attempt to avoid what we refer to as integration hell. Hours (not days or weeks) later. 3. Because there is always the possibility that you didn’t update your local copy correctly. it’s important to have a central continuous integration server that executes automatically. fetch and merge the latest changes.Chapter 18: Continuous Integration With Travis CI Travis⁵¹ is a popular open-source hosted continuous integration service that offers support for a number of languages (originally beginning with Ruby).org/ ⁵³http://git-scm. huh? Continuous integration? Definition: Popularized by Kent Beck and Martin Fowler. If the tests pass. Check out a copy of the source. you’ve successfully integrated. even if you don’t realize it. what. These days. including PHP. like Travis or Jenkins⁵².org ⁵²http://jenkins-ci. Wait. So chances are. 4. though. push your changes to GitHub. Create a branch and perform the bug fix or addition. Continuous integration assumes that you and your team leverage a version control system. Rinse and repeat. Each integration is then verified by an automated build server. a CI server will automatically build the project again (in an environment that’s as close to production as possible) and trigger all of the tests. ⁵¹http://travis-ci. 2. If no collisions occur (meaning the tests still pass).com/ . Item number five above might be the only step (an important one) that you don’t follow in your current workflow. and a core Extreme Programming development methodology. when the tests are green. 6. only integrate when the build is successful. terms like source control were scary things. 1. it’s a basic part of our development process. “Any individual developer’s work is only a few hours away from a shared project state and can be integrated back into that state in minutes. you’ll be happy to know that Travis is a cinch to use: connect to GitHub. GitHub. for each pull request. register a service hook. Pretty neat. like Travis. Git.com/articles/continuousIntegration. data stores and so on. hosting your project on travis-ci. Node.Chapter 18: Continuous Integration With Travis CI 247 Travis CI is one such example. “Our CI environment provides multiple runtimes (e.html ⁵⁵http://about. Though it may seem that continuous integration is primarily for development teams.org means you can effortlessly test your library or applications against multiple runtimes and data stores without even having all of them installed locally.js or PHP versions). Next. etc.Martin Fowler⁵⁴ One advantage to this rapid integration is that it encourages developers to break their work into small. the first step is to connect your GitHub account to Travis. The ultimate goal is to have a system for detecting errors within a matter of hours. Any integration errors are found rapidly and can be fixed rapidly.g. ⁵⁴http://martinfowler. 5.)? No problem: add a few characters to your configuration file and you’re done. services like Travis will prove to be quite helpful. this isn’t the case. PHPUnit (with tests). add a configuration file. Travis Imagine that you have a popular GitHub repository that others frequently contribute to. Even as a solo developer.org⁵⁵ If you’re worried that I’ve yet again introduced another tool to learn (when will it end). Hello. right? But what if you want to test the package against multiple versions of PHP (5. and you’re done! Three simple steps. 1.3. manageable chunks. and Travis provide the perfect recipe for integrating (excuse the pun) CI into your team’s daily workflow.org/docs/user/getting-started/ . Connect To get started. would notify you if their committed changes have broken your code. a service.4.” . Because of this. think of it is as the teacher who checks over your work.travis-ci. rather than days or weeks. imagine if.” .travis-ci. and enable one of your GitHub packages that includes tests (you can create a dummy project for testing purposes). Register Hooks Next. Tip: To manually register service hooks (such as for Packagist) in GitHub. In the screenshot below. I’m activating tests for my “Test Helpers” package that we reviewed in the testing models chapter. click Service Hooks.Chapter 18: Continuous Integration With Travis CI 248 Connect Travis to GitHub 2. Register the service hooks . visit the Settings page for one of your repositories. Travis will automatically register the necessary service hook with GitHub. view your profile. When checked. and scroll down to the applicable service. setup work.5. some applications may require a test database.yml file. Which language are we working with? Which versions of that language must this project be tested against? Anything we need to do before running the tests? Which test script should be used? Overall.php composer.yml file. Here’s the absolute essentials for a basic PHP project that is dependent upon Composer. Configure As the final step. 3.Chapter 18: Continuous Integration With Travis CI 249 3. you have to tell Travis a bit about your project. the framework will automatically generate a travis.5. if it doesn’t know which language.org/installer | php . placed in the root of your application.curl -s http://getcomposer. it’s fairly simplistic! Remember: this is a Yaml file. All of this can be declared within a travis. so pay close attention to your indentation.3 6 7 8 9 before_script: . 4. and tools it was built with? Further. How can it run your tests. .4 .phar install --dev 10 11 script: phpunit This file makes a handful of declarations: 1. 1 language: php 2 3 4 5 php: . 2. Did You Know: When creating a package with Laravel’s workbench. and more. version. commit your changes and push them to GitHub. . Here’s an example of one I received today. in turn.Chapter 18: Continuous Integration With Travis CI 250 Configuring Travis Upon adding this new file. you’ll receive an email with the results. which. will read the project’s configuration file and execute its tests. Shortly after. Once GitHub registers the commit. notifying me that the tests all passed. it will fire off a notification to Travis (your service hooks at work). detailing the problem. Travis will provide a stack trace. When the tests invariably fail. when green isn’t the color of the day.Chapter 18: Continuous Integration With Travis CI 251 Travis will email you the results of the latest integration. There will be plenty of times. though. . That’s what makes it so useful.yml file). Travis is a fairly straight-forward tool. . there will certainly be projects that require extended configuration (within your travis.Chapter 18: Continuous Integration With Travis CI 252 Error reports in Travis Build Configuration As I noted earlier. Having said that. Absolute Basics If nothing else. This section will outline various common options. Travis needs to know the language and version of the project. json file.5.Chapter 18: Continuous Integration With Travis CI 1 2 3 253 language: php php: . too.php composer. test against version 5. against PHP version 5. 1 2 before_script: ./bin/setup-dependencies. simply add one line: 1 2 3 4 language: php php: . per usual.xml file. should be added to before_script. will generate this file for you.php.curl -s http://getcomposer. say.phar install --dev Maybe you have a dedicated shell script that will install or prepare various dependencies. To.2 Bootstrapping When running PHPUnit.5. Perhaps you need to execute an Artisan command first: . 1 2 3 before_script: . such as Composer’s vendor/autoload. This sort of configuration should be placed. within your phpunit.2 as well. The following snippet will download Composer and install all dependencies that are declared within your project’s composer.5. Travis will test your project with PHPUnit.4 With these three lines alone. it will build an environment. you will often want to register a bootstrap file. As such. Register Dependencies Each time that Travis runs your repo’s tests.4 ..org/installer | php . Remember: Laravel. you must explicitly declare all dependencies or commands that should be executed before running the test script.4. That. Anything that you would write in the Terminal may be used here.sh Notice that these are just simple commands. too. based upon the configuration file. Nonetheless. To his configuration file. Are you? Once again. please don’t let two words keep you from sleeping better at night. Let’s say that Taylor wanted Travis to send through the results of each commit to Laravel’s IRC channel.org/installer | php .freenode.php composer. it’s a pattern that all modern development teams follow in 2013. .curl -s http://getcomposer. and integrate Travis CI into your development process today. However.Chapter 18: Continuous Integration With Travis CI 1 2 3 4 254 before_script: .phar install --dev . he could add: 1 2 3 notifications: irc: .logs@example. Software development is confusing enough. do we really need to introduce this much confusing terminology? That said. reread this chapter.com IRC Travis can even update your team’s IRC channel with each commit. Travis will send email notifications for each commit to the commit author and repository owner.devmanager@example. Take a few hours. it’s an unfortunate truth that scary jargon like this can deter developers.com . Turn Off Notifications 1 2 notifications: email: false Set Recipients 1 2 3 4 5 notifications: email: .php artisan migrate --seed Notifications By default.net#laravel" Summary Continuous integration is not Laravel specific."chat. this can be modified if necessary.com .steve-micromanager@example. Rather than grouping it directly within a particular method. Depending on when you made your purchase. you have two options to allow for testability. I’m sure you have questions. 1 2 3 4 public function getContents($file) { return "The content of the given file is: " . this function isn’t testable without a bit of trickery. I guarantee it! Think of this chapter as a living document that will frequently be updated. email. } Unfortunately. or GitHub. this chapter will either be sparse. How Do I Test Global Functions? As a basic rule of thumb. .you made it this far? Well. Consider this dummy method that returns the contents of a file. make every effort to avoid wrecklessly using global functions within your code. Mock It Let’s imagine that a piece of your code uses the native file_get_contents() function. instead. If you do. Let’s fix that by extracting the global function reference. file_get_contents($file). or massive! 1. extract it to its own method that you can then mock. In fact. though.Frequently Asked Questions (A Living Document) Wow . as I find myself responding to the same questions on Twitter. $sentence). $this->fetch($file). 3 4 5 6 7 8 9 10 $sentence = $mock->getContents('foo'). 1 class TheClassTest extends PHPUnit_Framework_TestCase { 2 public function testGetContents() { $mock = Mockery::mock('TheClass')->makePartial(). } Now that we have a wrapper for this function. 2 3 4 5 6 function showTime() { return time(). Here’s an example: 1 <?php namespace App\Helpers. } 5 6 7 8 9 public function fetch($file) { return file_get_contents($file). To simplify things. we’ll assume a collection of functions within a namespace. getContents is a cinch to unit test. 13 } 14 15 16 } The Namespacing Trick A second solution is to use namespacing. $mock->shouldReceive('fetch') ->once() ->with('foo') ->andReturn('bar'). App. } . this time. 11 12 $this->assertEquals('The contents of the given file is: bar'.Frequently Asked Questions (A Living Document) 1 2 3 4 256 public function getContents($file) { return "The contents of the given file is: " . Frequently Asked Questions (A Living Document) 257 In its current state. In fact. app/tests/_data/dump. 2 3 function time() { return 'foo'. first attempt to create a wrapper for the function that can be mocked. PHP will move up and assume that we’re referring to the global version of time(). we’re essentially create App\time(). 1 <?php namespace App. } With this modification. 1 <?php namespace App\Helpers. How Do I Create a SQL Dump for Codeception Tests? We reviewed this in Chapter 16.sql. hopefully. the basic principle has shown through. If you cannot remove a global function but still need to test a particular method. However. But. As no time() function exists in the current namespace. use the namespacing trick. triggering the showTime() function will. we can add a custom time() function that will override the default version. 2. showTime()). For example. this is a silly example. } 4 5 6 7 8 function showTime() { return time(). run: . return a time stamp. } } Admittedly. to dump a database. foo will be returned! Let’s use this in our test. from the command line. In effect. as expected. 2 3 function time() { return 'foo'. we’re not really testing anything! Always be careful of that when mocking. As a fallback. } 4 5 6 7 8 9 10 class FunctionsTest extends PHPUnit_Framework_TestCase { public function testShowTime() { $this->assertEquals('foo'. The easiest way is to knock it out in a single command. when calling showTime(). called my-db into a file. if your setter manipulates or validates the data in some way. choose File -> Export. we’ll likely have a more universal theory of which tests to write. […] Ten or twenty years from now. 4. However. if they don’t work properly. You test a route. How Do I Test Protected Methods? As a basic rule of thumb. it’s ignored. If using the popular Sequel Pro⁵⁶ app. only test the public interface. your public tests will fail. In the meantime. throw a test in there to ensure that it works as expected. My rule is that. ⁵⁶http://www. for some reason. As such. Why Is PHPUnit Ignoring My Filters? This is a common pitfall when functional testing. the community is a bit divided on this one. Luckily. Should I Test Getters and Setters? Once again. there are plenty of MySQL GUIs that will do the job for you. but find that. for some reason. if you’d like my personal advice. I am of the opinion that they’re unnecessary in 90% of the cases. and how to tell the difference. experimentation seems in order.” . “I get paid for code that works. which tests not to write.Frequently Asked Questions (A Living Document) 1 2 258 mysqldump --opt --user="USERNAME" --password="PASSWORD" my-db > app/tests/_data/d\ ump. don’t waste time writing a test. 3.com/ ⁵⁷http://stackoverflow. and as with many things in the testing world. If I don’t typically make a kind of mistake (like setting the wrong variables in a constructor). if there’s no real logic going on. I don’t test for it.com/questions/153234/how-deep-are-your-unit-tests/153565#153565 . so my philosophy is to test as little as possible to reach a given level of confidence (I suspect this level of confidence is high compared to industry standards. Those protected methods will be tested indirectly in the process. the fix is an easy one.sequelpro. not for tests. the mysqldump executable isn’t available to you. expect the filter to be applied. Opinions vary a bit on this issue.sql If. but that could just be hubris). you don’t test them. but.Kent Beck⁵⁷ 5. what you need is a partial mock.twitter. 6 public function __construct($searchTerm) { $this->searchTerm = $searchTerm... Can I Mock a Method in the Class I’m Testing? This will happen from time to time: you’re testing a class.Frequently Asked Questions (A Living Document) 1 2 3 259 public function testSomeRoute() { Route::enableFilters(). 'api/v1/posts'). but need to mock one method within that class. 6. 14 15 16 17 return file_get_contents($url). 18 } 19 20 } When testing this class. this is a cinch. With Mockery⁵⁸. $this->searchTerm). } 7 8 9 10 11 public function compile() { /* .com/padraic/mockery .com?q='. 5 6 } The Route class’ enableFilters method should activate these filters when testing. ⁵⁸https://github. Let’s review a bit of pseudo code for querying Twitter’s Search API. 4 // $this->call('GET'. you may decide to stub or mock that fetch method to prevent the code from querying the Twitter API. protected $searchTerm. 1 <?php 2 3 4 5 class TwitterSearch { protected static $url = 'http://search. */ } 12 13 public function fetch() { $url = urlencode($this->url . // returns foo Though this won’t always be the case. This code will only mock the fetch method on the TwitterSearch class. Because of this. this certainly holds true. $mock->fetch(). 1 2 $mock = Mockery::mock('TwitterSearch')->makePartial(). unless an expectation is declared. should be extracted to its own class. Passive Partial Mocks An alternative approach is to use what Mockery refers to as passive partial mocking. // calls real method 3 4 5 6 $mock = Mockery::mock('TwitterSearch')->makePartial(). Then. the difference in this approach is that the presence of an expectation will determine if that method is mocked. $mock->shouldReceive('fetch')->once()->andReturn('stub'). or defaults to the original implementation. which simply returns the contents of a file as a string. to reiterate. if you find that you’re leveraging partial mocks too frequently. TwitterSearch could merely ask for that functionality upon instantiation. Traditional Partial Mocks The easiest way to mock that fetch method is to use a traditional partial mock. leaving the remainder free to respond to calls as they normally would. foo will be returned. This declares that all methods on the mock object should defer to the original implementation. 1 $mock = Mockery::mock('TwitterSearch')->makePartial(). when the fetch() is triggered at some point in the object. ['laracon']). leaving all others untouched and free to behave as they normally would when instantiated. allowing for an easy test double. The fetch method. $mock->fetch(). it’s important to pass in any constructor arguments in the form of an array. like so: 1 2 $mock = Mockery::mock('TwitterSearch[fetch]'. as the second argument. leaving the Twitter API untouched. this might be an indicator that your code should be refactored to better follow the single responsibility principle. . So. In the case of the above example. Now.Frequently Asked Questions (A Living Document) 260 Definition: A Partial Mock is useful when you only need to mock a couple of the methods on an object. $mock->shouldReceive('fetch')->once()->andReturn('stub'). use camelCasing for your method names. This is more in line with how PHP. That’s your job. in favor of using underscores (even though we didn’t do so in this book). So make up your own mind. I think there’s a lot of merit to ignoring that rule. I Prefer Using Underscores For Test Names. is structured. But. itself. . for all production code. Is That Okay? Of course it is! Don’t let a book define how you approach your code. when it comes to writing tests. Be honest: which of the following method names is easiest to read? 1 2 3 public function testDeletesTasksWhenUserCancels() {} // or public function test_deletes_tasks_when_user_cancels() {} None of these guidelines is etched in stone.Frequently Asked Questions (A Living Document) 261 7. As a best practice. I worked hard on this book! The truth.com/JeffreyWay/Laravel-Testing-Decoded/issues/1 ⁶⁰https://github. you see testing in your future. just maybe.this isn’t the end! We’re just getting started. You know what? It better dang well be in your future. spend much of my days making ⁵⁹https://github. Let’s be friends! I’m incredibly active on Twitter.and in closing . Even a sentence or two would be appreciated. I hope the same will be true for you! Testimonials: If you enjoyed the book.com/JeffreyWay/Laravel-Testing-Decoded/issues . but still find myself learning new tricks and finding pitfalls every day. Wait a Second… But wait . though. and wouldn’t mind providing a short testimonial that will be used in various promotional spots around the web. I suppose this brings us to a close (cue the closing Saturday Night Live music). is that this is an incredibly complex topic that consists of multiple methodologies. and. Laravel Testing Decoded wasn’t meant to be released and forgotten. One of the first supplementary chapters will focus on building the purchase website for this very book.Goodbye Well. No. not only will it continue to be updated to reflect new testing functionality in the Laravel framework.I’ll leave you with one question: what do you want to learn next?⁶⁰ Stay in Touch . I’ve been at this for a long time. Rest assured that. but I also plan to release new chapters throughout 2013. This will provide plenty of real-world scenarios to dig further into acceptance and unit testing. the book lived up to what you expected. and. terminologies. do so in this thread⁵⁹. frameworks. helpers. of course. it’s not a skill that you pick up in a few hours. Hopefully. So. with that in mind . and more. com . 263 .Goodbye Tuts+ Premium the best educational site on the web.com/jeffrey_way http://tutsplus. Don’t be a stranger! http://twitter.
Copyright © 2024 DOKUMEN.SITE Inc.