In the framework we're using, there are several operations that are performed considering the class name.
So, while testing, I have to force the mock classname, doing something like this:
$this->getMock('Model', $methods, array($config), 'ModelFoobars');
Sadly I just found out that doing this will pollutes my following tests. This is very strange, since there is no internal cache (singleton pattern) in the objects I'm curently testing.
However, if I debug my test, I can find that my $model variable is an instance of PhpUnit, even if it was created using a new syntax!
$classname = 'ModelFoobars';
$model = new $classname();
I have no opcode cache, nor anything like that. This is really driving me crazy, any suggestions?
After a lot of testing and debugging (and I really mean a lot), I finally found the problem.
When you create a new mocked object, under the hood PhpUnit is dynamically creating new code, then it evals it.
By default, PhpUnit creates random class names, so you usually don't have any problems; however, if you force the class name, you can have "funny" results. Since the code is evaled, the class is pushed into the global scope!
This means that once you created the mock, you can't create another one with the same name: you are stuck using the first one.
The only solution I found is to avoid using fixed class names and always rely on the random ones.
Related
The issue
I have an unexpected warning from PHPStorm when I try to set a new value in a PHP-DI container.
Given the following code:
function inject(Psr\Container\ContainerInterface $container){
$container->set(RandomClass::class, new RandomClass());
}
$container = new DI\Container(); class is instantiated
inject($container);
The following warning is triggered
Potentially polymorphic call. does not have members in its hierarchy
I understand what the warning means, but I do not see why it pops up, especially since I have not found any occurrences of this situation while looking on Google and SO and the documentation does not mention it.
Is there something I am missing, or is this a "false positive" ?
The set() method is not part of Psr\Container\ContainerInterface.
If you want to use that method, you can't typehint against the interface because your code explicitly needs a PHP-DI instance.
Your code doesn't have to be generic, don't overthink things too much. The PSR is useful mostly for frameworks and libraries (who need to be compatible with multiple containers), not for end-users.
The day you switch container library you will have many more complex things to do than just replacing the set() call.
The reason behind the issue
Given the following code (which is very similar to the one I use)
function inject(Psr\Container\ContainerInterface $container){
$container->set(RandomClass::class, new RandomClass());
}
$container = new DI\Container(); class is instantiated
inject($container);
The $container->set(...) call is going to trigger the following warning
Potentially polymorphic call. does not have members in its hierarchy
This is to be expected as Psr\Container\ContainerInterface only contains definitions for the following methods
get($id)
has($id)
The solution
Two possible solutions for this issue:
Type the methods directly with the container, making sure to not use the FQN of the class but only use Container and "use the namespace", it will make changing to a new container package easier (because this is still the goal behind PSRs, being able to almost hot-swap packages).
Create a custom interface based on Psr\Container\ContainerInterface and add the required methods to it.
Or, eventually, you can try to make PHP-FIG extend the PSR-11 standard to include a standard set($id, $value) method.
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 am trying to test a class that has other classes passed to it (dependency injection), which are stored as class properties. (A settings class, for example that is the result of a Factory).
So, in psuedo code, this is what I am dealing with:
//Get settings for this user.
$settings = SettingsFactory::GetSettings();
//Create widget that uses settings
$widget = new Widget($settings);
Now, obviously, if we refactor code and break SettingsFactory::Getsettings(), the constructor for Widget will fail. But, not necessarily because anything is wrong with Widget.
So, I want to test these components IN ORDER, skipping "later" tests when "early" tests fail.
Consider my current structure for tests:
tests\vendor\Settings\SettingsTest.php
tests\vendor\Widget\WidgetTest.php
PHPUnit needs to test SettingsTest.php (and have everything pass) before running WidgetTest.php will be meaningful.
I think I need to use #depends, but that seems to be limited to a single class scope.
This question seems to hold a piece of the puzzle, but I am not sure how to implement.
How do I write (structure) these tests?
One of the big benefits of DI is that you can avoid issues like this, by injecting a mock $settings object that behaves the way you want. This gives you a true unit test of Widget without worrying about the implementation details of Settings:
$mockSettings = $this->createMock(Settings::class);
$mockSettings->method('someMethod')->willReturn('something');
$widget = new Widget($mockSettings);
// assertions here
Background: I'm working on an MVC framework for some practice, and want to make sure everything is 100% unit tested.
The setup currently is to have an instance of the application class (Ex_App). The main script asks a Dispatcher/Router for a controller name. This controller name is the name of a class implementing Ex_Controller. The result is returned as an instance of Ex_Dispatch_Result. This result is passed to the Ex_App instance using an invokeController($dispatchResult) function.
And this is where magic happens. The listing below is an excerpt:
$controllerName = $dispatchResult->getControllerName();
... checks for validaty of class name ...
$controller = new $controllerName();
$controller->prepare($this);
I'm using PHPUnit to do my unit testing, and am able to mock the dispatch result, correctly check that validating the class name of the controller works. The problem is how to check if prepare is called.
I'd like to do something similar to:
$mockController = $this->getMockBuilder('Ex_Controller')
->setMockClassName('Invoke_Correct_Controller')
->getMock();
$mockController->expects($this->once())->method('prepare');
However since a new instance of Invoke_Correct_Controller is created upon calling invokeController, it will not be this mock and thus the expects() call is completely irrelevant.
I could make the Ex_Dispatch_Result class responsible for returning a controller and testing that, but before returning an instance I will need to verify the correctness of the class name and in my opinion that responsibility should be with the Ex_App class and not the "dumb shell" Ex_Dispatch_Result class.
Is there something I am missing in the PHPUnit framework that I could use to test the code here, or some useful pattern that could work in my instance? I feel passing around controller names scales way better than passing around instances of controllers from the start, requiring the initialization of every possible controller. So, I kinda want to stick to passing around names and using the Ex_App as a factory for the controller instance.
Maybe I'm just over-thinking part of this problem, but that happens sometimes. It's why a fresh look by a third party often works :-)
There are couple of things you could do:
Extract controller creation logic to separate class e.g. ControllerFactory, and then mock controller factory instance, so that it returns your $mockController.
Extract controller creation logic to separate method and use partial mocking.
Return $mockController from $dispatchResult->getControllerName(), which probably requires mocking of $dispatchResult or even something else.
If you want more detailed answer, please provide more code samples of your classes and methods.
PHPUnit's getMock($classname, $mockmethods) creates a new object based on the given class name and lets me change/test the behavior of the methods I specified.
I long for something different; it's changing the behavior of methods of an existing object - without constructing a new object.
Is that possible? If yes, how?
When thinking about the problem I came to the conclusion that it would be possible by serializing the object, changing the serialized string to let the object be instance of a new class that extends the old class plus the mocked methods.
I'd like some code for that - or maybe there is such code already somewhere.
While it would certainly be possible to create the to-be-mocked object again, it's just too complicated to do it in my test. Thus I don't want to do that if I don't really really really have to. It's a TYPO3 TSFE instance, and setting that up once in the bootstrapping process is hard enough already.
I know this answer is rather late, but I feel for future viewers of this question there now exists a simpler solution (which has some drawbacks, but depending on your needs can be much easier to implement). Mockery has support for mocking pre-existing objects with what they call a "proxied partial mock." They say that this is for classes with final methods, but it can be used in this case (although the docs do caution that it should be a "last resort").
$existingObjectMock = \Mockery::mock($existingObject);
$existingObjectMock->shouldReceive('someAction')->andReturn('foobar');
It acts by creating a proxy object which hands all method calls and attribute gets/sets to the existing object unless they are mocked.
It should be noted, though, that the proxy suffers from the obvious issue of failing any typechecks or typehints. But this can be usually avoided, because the $existingObject can still be passed around. You should only use the $existingObjectMock when you need the mock capabilities.
Not all pre-existing code can be tested. Code really needs to be designed to be testable. So, while not exactly what you're asking for, you can refactor the code so that the instantiation of the object is in a separate method, then mock that method to return what you want.
class foo {
function new_bar($arg) { return new bar($arg); }
function blah() {
...
$obj = $this->new_bar($arg);
return $obj->twiddle();
}
}
and then you can test it with
class foo_Test extends PHPUnit_Framework_TestCase {
function test_blah() {
$sut = $this->getMock('foo', array('new_bar', 'twiddle'));
$sut->expects($this->once())->method('new_bar')->will($this->returnSelf());
$sut->expects($this->once())->method('twiddle')->will($this->returnValue('yes'));
$this->assertEquals('yes', $sut->blah());
}
}
Let me start of by saying: Welcome to the dark side of unit testing.
Meaning: You usually don't want to do this but as you explained you have what seems to be a valid use case.
runkit
What you can do quite easily, well not trivial but easier than changing your application architecture, is to change class behavior on the fly by using runkit
runkit_method_rename(
get_class($object), 'methodToMock', 'methodToMock_old'
);
runkit_method_add(
get_class($object), 'methodToMock', '$param1, $param2', 'return 7;'
);
runkit::method_add
and after the test to a method_remove and the rename again. I don't know of any framework / component that helps you with that but it's not that much to implement on your own in a UglyTestsBaseTest extends PHPUnit_Framework_TestCase.
Well...
If all you have access to is a reference to that object (as in: The $x in $x = new Foo();) i don't know of any way to say: $x, you are now of type SomethingElse and all other variables pointing to that object should change too.
I'm going to assume you already know things like testing your privates but it doesn't help you because you don't have control over the objects life cycle.
The php test helpers extension
Note: the Test-Helper extension is superseded by https://github.com/krakjoe/uopz
What also might help you out here is: Stubbing Hard-Coded Dependencies using the php-test-helpers extension that allows you do to things like Intercepting Object Creation.
That means while your application calls $x = new Something(); you can hack PHP to make it so that $x then contains an instance of YourSpecialCraftedSomething.
You might create that classing using the PHPUnit Mocking API or write it yourself.
As far as i know those are your options. If it's worth going there (or just writing integration / selenium tests for that project) is something you have to figure out on your own as it heavily depends on your circumstances.