I am having trouble testing certain template parts in WordPress. WordPress template parts are essentially PHP files that are simply included with require/include when you dig deep enough.
This should be covered by end-to-end tests, but because sometimes these template parts contain some logic, I'd like to do some basic unit testing. These templates create instances of various classes and use them to generate output. So for testing purposes I wanted to mock those classes, however my mocked classes are constantly ignored and templates will always call original classes.
I suspect that there are some namespace shenanigans going on, but I really can't figure out where and how.
I made this little test to reproduce this behaviour.
Class method that needs to be mocked in the test:
namespace FooLib;
class NeedsMock {
public function getStuff() {
return "I have returned.";
}
}
Main file that creates an instance of the above class and calls a method from it:
require_once "./vendor/autoload.php";
use \FooLib\NeedsMock;
$stuff = new NeedsMock();
$result = $stuff->getStuff();
echo "I'm a little teapot.\n";
echo "Result: " . $result . "\n";
And the test class which tests what main.php is outputting:
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use FooLib\NeedsMock;
class MainTest extends TestCase {
use MockeryPHPUnitIntegration;
public function test_main() {
$mock = \Mockery::mock(NeedsMock::class);
$mock->shouldReceive('getStuff')->andReturn('Wherever you go, there you are.');
ob_start();
require_once "main.php";
$res = ob_get_clean();
$this->assertStringContainsString("Wherever you go, there you are.", $res, "It's good.");
}
}
First I create a mock of NeedsMock and mock the output of getStuff() then I capture the output of main and compare the expected result. Test fails because in main.php creates an instance of NeedsMock and doesn't use the mocked object.
I am aware that this is not the correct approach for testing things like this, but at the moment this is something I have to work with, so I'd like to cover a few things while we're in the process of refactoring everything and before end-to-end tests are written.
Is it possible to mock classes so that file included in the test will use those mocks?
Related
I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.
I'm new to PHPUnit. I have php files that don't have any classes in them. What I came to understand from reading documentation, PHPUnit considers a class as a single unit.
So Does PHPUnit considers a class as a unit?
Is it possible to test php files that don't have any class in them?
Sure, you can absolutely test other PHP scripts.
class MyScriptTest extends PHPUnit_Framework_TestCase {
public function testMyFunction() {
include_once 'path/to/script.php';
$result = someFunction();
$this->assertEquals('expected result', $result);
}
}
So write PHPUnit test classes, and inside a test run whatever code you wish to test and make assertions against it.
Maybe the question comes across as weird, but here is the problem that I'm trying to solve... first of all, please keep in mind I am more of a Java developer getting used to working with PHP, so maybe my thought process is the problem!
I am testing a web site that I have built with Symfony. For my component testing, I create my test class extending WebTestCase and my test I have a set of assertions like the following to verify that the page title is where I want it and containing what I expect:
$text = "Page Title";
$selector = "h2#pageHeading";
$this->assertEquals(1, $crawler->filter($selector)->count(), "Found wrong number of elements using selector ".$selector);
$this->assertEquals(trim($text),
trim($crawler->filter($selector)->text()),
"Element $selector didn't have expected text");
Then I write more tests for other pages within the site, and in all of them I want to test the title is there as it should be, so to be able to reuse code I refactor the above into a function in parent class that the other test classes reuse:
function assertPageTitle($text) {
$selector = "h2#pageHeading";
$this->assertEquals(1, $crawler->filter($selector)->count(), "Found wrong number of elements using selector ".$selector);
$this->assertEquals(trim($text),
trim($crawler->filter($selector)->text()),
"Element $selector didn't have expected text");
}
And I call that method in my tests. As tests develop there are more similar "complex assertions" that I can refactor, and all of them go to the parent class, thus bloating my parent class into a massive assertion container:
protected function assertSelectedOptionContainsTextValue($selector, $text, $value, $crawler) {
...
}
protected function assertMenusContainItems($menus, $crawler) {
...
}
protected function assertErrorMessageShown($message, $crawler) {
...
}
... (more and more) ...
You get the idea. My next thought at this point is to refactor all this "complex assertions" into other classes, probably following the Page Object pattern, but then the other classes won't have access to the assertEquals method unless those other classes also extend WebTestCase or at least PHPUnit_Framework_TestCase, which doesn't seem a very good idea...
So is there an easy way to access the assertEquals method (and related) without having to extend the base PHPUnit classes? Can I use composition somehow?
PHPUnit's built-in assertions are implemented as public static methods in the PHPUnit_Framework_Assert class. Just invoke them as PHPUnit_Framework_Assert::assertTrue(), for instance.
First of all, you are overwriting $text variable at the beginning of your "generic" function - but I understand that you want to only show idea.
Secondly, using unit testing to this kind of tests isn't the best choose. I think you should extract logic of titles to separated classes and then test it by PHPUnit or use more appropriate testing solutions like Behat or Selenium.
If you still haven't changed your opinion... You must extend at least PHPUnit_Framework_TestCase whenever you want to use PHPUnit to testing.
Little hint: Traits would be good to extract your assertions.
In order to create reusable phpunit assertion in trait. That can be used in many tests cases. You can use trait that statically calls PHPUnit_Framework_Assert::assertSame, quick example how to achieve it:
<?php
namespace Tests\Foo;
use PHPUnit_Framework_Assert;
trait AssertTrait
{
public function assertInTraitAlwaysFailing()
{
PHPUnit_Framework_Assert::assertSame(1, 2, 'always failing');
}
}
It would act as helper that can be used in many places where you need same assertion to prevent duplication.
I have a method with some logic in it and I'm not sure how to unit test it. Because it's a unit test for this specific method it should run without connecting to the database. I read about stubs and mockups but I can't find a way to apply them to this situation.
I would like to force the Client:GetClient to return the client object with the right properties so I can test each logic branch.
class ClientType {
function GetClientType($id) {
$objClient = Client::GetClient($id);
if ($objClient->Returning == 1) {
return 'returning';
}
else {
return 'normal';
}
}
This is the test I had in mind
class ResourceTest extends PHPUnit_Framework_TestCase {
function testGetClientType() {
$objClientType = new ClientType();
$this->assertTrue($objClientType->GetClientType(100), 'normal');
}
}
The problem is the dependency $objClient = Client::GetClient($id); The GetClient will pull a client from database but I need to replace this with a Stub so the unit tests work without real access to the database.
Conclusion
If you have code like the one presented: refactor it and use Dependency Injection.
If you have legacy code or just don't want to refactor try this solution: http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
With PHPUnit you can do
$class = $this->getMockClass(
'Client', /* name of class to mock */
array('getClient') /* list of methods to mock */
);
$class::staticExpects($this->any())
->method('getClient')
->will($this->returnValue('foo'));
In general, you want to avoid static methods though:
http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html
https://kore-nordmann.de/blog/0103_static_considered_harmful.html
EDIT after update
PHPUnit can also stub hardcoded dependencies. See
Stubbing Hard-Coded Dependencies by Sebastian Bergmann
However, since you already noticed by now that it is a Pain the Behind to test statics and hardcoded dependencies, I suggest you remove the hardcoded dependency and static call with a real object that you inject into your ClientType instead.
Another option would be to use http://antecedent.github.io/patchwork (not affiliated with it), which
is a PHP library that makes it possible to redefine user-defined functions and methods
at runtime, loosely replicating the functionality runkit_function_redefine in pure PHP 5.3 code,
which, among other things, enables you to replace static and private methods with test doubles.
Try Mocking for such kind of a problem. I do not know PHP, but we do mocking in Java and C# in such a scenario.
EDIT
I'm sorry I didn't see that you were facing a problem with mocks. Well usually for Mocks you tell them what you expect from them and they respond to the same. LSV principle is being used in mocks. As for PHP, I'm sorry I have no idea on what tool to use for mocks and how you use them
Why would we use the class_alias function? For example:
Class Test {
public function __construct(){
echo "Class initialized";
}
}
class_alias("Test", "AnotherName");
$instance = new AnotherName(); # equivalent to $instance = new Test();
According to the manual, "The aliased class is exactly the same as the original class."
What is this useful for?
Surprisingly, nobody has mentioned the obvious reason why one would do this: the use keyword can only be used in the outmost scope, and is processed at compile-time, so you can't use Some\Class based on some condition, nor can it be block-scoped:
namespace Foo;
if (!extension_loaded('gd'))
{
use Images\MagicImage as Image;
}
else
{
use Images\GdImage as Image;
}
class ImageRenderer
{
public function __construct(Image $img)
{}
}
This won't work: though the use statements are in the outmost scope, these imports are, as I said before, performed at compile-time, not run-time. As an upshot, this code behaves as though it was written like so:
namespace Foo;
use Images\GdImage as Image;
use Images\MagicImage as Image;
Which will produce an error (2 class with the same alias...)
class_alias however, being a function that is called at run-time, so it can be block scoped, and can be used for conditional imports:
namespace Foo;
if (!extension_loaded('gd'))
{
class_alias('Images\\MagicImage', 'Image');
}
else
{
class_alias('Images\\GdImage','Image');
}
class ImageRenderer
{
public function __construct(Image $img)
{}
}
Other than that, I suspect the main benefit of class_alias is that all code written, prior to PHP 5.3 (which introduced namespaces) allowed you to avoid having to write things like:
$foo = new My_Lib_With_Pseudo_Name_Spaces_Class();
Instead of having to refactor that entire code-base and create namespaces, It's just a lot easier to add a couple of:
class_alias('My_Lib_With_Pseudo_Name_Spaces_Class', 'MyClass');
To the top of the script, along with some //TODO: switch to Namespaces comments.
Another use case might be while actually transferring these classes to their namespaced counterparts: just change the class_alias calls once a class has been refactored, the other classes can remain intact.
When refactoring your code, chances are you're going to want to rethink a couple of things, so a use-case like aichingm suggested might not be too far fetched.
Last thing I can think of, but I haven't seen this yet, is when you want to test some code with a mock object, you might use class_alias to make everything run smoothly. However, if you have to do this, you might aswell consider the test a failure, because this is indicative of badly written code, IMO.
Incidently, just today I came across another use-case for class_alias. I was working on a way to implement a lib, distilled from a CLI tool for use in a MVC based web-app. Some of the classes depended on an instance of the invoked command to be passed, from which they got some other bits and bolts.
Instead of going through the trouble of refactoring, I decided to replace the:
use Application\Commands\SomeCommand as Invoker;
Statements with:
if (\PHP_SAPI === 'cli')
{
class_alias('\\Application\\Commands\\SomeCommand', 'Invoker');
}
else
{
class_alias('\\Web\\Models\\Services\\SomeService', 'Invoker');
}
and, after a quick :%s/SomeCommand/Invoker/g in vim, I was good to go (more or less)
Think of this the other way. There is a library and it contains a class called MySq, as the library moves on in development they think 'oh maybe we should just make one database class' so the rewrite the class and call it 'DatabaseConnection' it has the same functions as the MySql class but it can also handle Sql and MsSql. As time goes on and they release the new version 'PHPLib2'. The developer has now two options: 1, tell the users to change from MySql to DatabaseConnection or 2, the developer uses
class_alias("DatabaseConnection","MySql");
and just mark the class MySql as deprecated.
tltr;
To keep version compatibility!
It can be used for:
Changing strange class names of ext. scripts/sources (without the need to rewrite the code)
Making class names shorter (the class alias can be used application wide)
Moving classes to another namespace (#tlenss)