Interface and Traits does not work properly - php

This is some weird behaviour I witnessed today and I wonder why.. I`ll get straight to the point:
So we are using a2lix translations with symfony and fos elasticabundle
class Class
{
use Translatable;
}
class ClassTranslation implements \A2lix\I18nDoctrineBundle\Doctrine\Interfaces\OneLocaleInterface
{
use Translation;
}
In this scenario once I run
php app/console fos:elastica:populate
I`m getting:
[InvalidArgumentException] Parameter 'locale' does not
exist.
But the parameter is actually in the trait(from the vendor that include 2 traits translatable methods and translatable properties)
The questions is why does this happen. We have the property from the trait yet once we run the command it does not find it.
Ok now here comes the strange part!
In this scenario everything works as expected:
class Class
{
use Translatable;
}
class ClassTranslation
{
use Translation;
}
If I decide not to implement the interface the command runs smoothly, the interface just forces you to have getter and setter for the locale atribute. I just wonder why does this happen.

This may relates to https://github.com/a2lix/I18nDoctrineBundle/issues/16.
I don't think this is related to php traits since there wouldn't be an InvalidArgumentException. Maybe the code of the trait is not executed if the interface is not present, so there is no error in this case. Please check the implementation of your trait, maybe do some debug output to see when the code is executed and when the exeption occurs.

Related

PHPUnit gives error: Target [Illuminate\Contracts\View\Factory] is not instantiable

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.

Is there a way to detect if a PHPUnit test is running in background?

PHPUnit has two special annotations to indicate a test should run in a background process: a class annotation #runTestsInSeparateProcesses that affects all tests in the current class, and a test annotation #runInSeparateProcess that affects the current test.
This is useful in some cases when the tested class needs to print some output, so it would not mess up with PHPUnit's own output.
So, here's my question: is the TestCase class aware of this fact? Does it know when it's running on background? If so, is there a method to detect it?
I'm looking for something like $this->isRunningOnBackground().
I did not find anything about this in the documentation.
Browsing PHPUnit's source code, I found a boolean protected property in TestCase class $runTestInSeparateProcess that is set to true when the annotation is present. There's a $runClassInSeparateProcess as well, but it's set to private, so you won't access it in a child class context.
We can detect if a test is running on background by just checking if ($this->runTestInSeparateProcess) {...} inside setUp() or tearDown().

Laravel namespace issue

I have a laravel 4.1 application, and I've created a folder in my app folder to store most of the logic.
/app/Acme/Models/
/app/Acme/Repositories/
these are the two main folders.
In my composer.json I have this in the auto load, and done a dump run.
"psr-4" : {
"Acme\\" : "app/Acme"
}
However I am getting, what I think are silly issues. For example my Acme/Models/Task.php has the following
<?php
namespace Acme\Models;
class Task extends \Eloquent {
public function job()
{
return $this->belongsTo('Job');
}
}
however when I run this, I get an error
Fatal error: Class 'Task' not found in vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php on line 780
In my Job.php I have the same namespace at the top of the file....
Must I manually import/use object which are in the same name space?
use Acme/Models/Job as Job; ? it seems like such a duplicate...
And in my Repositories folder when I set a namespace of namespace Acme/Repositories;, must I use items like
use Acme\Models\Job as Job;
I is, a bit lost!
Namespaces are relative so you do not need to add use to directly reference classes within the same namespace.
The error you are getting is because you need to fully qualify relationships to namespaced models so eloquent knows where to find them eg
$this->belongsTo('\Acme\Models\Jobs');
In the case of your repository namespace, you will need to add a use statement in your file as you suggested, or reference the fully qualified namespace eg new \Acme\Models\Job();
On a side note, I know PHPStorm (and I'm sure other IDEs) will inject the namespaces for you which is super useful and saves you having to write use or the full namespace out every time you reference a class - worth checking out.
Edit: Sorry, I didn't read the question properly first time around - updated my answer.

PHP class not found in autoloaded implementation file

I really hope this isn't a duplicate, but here I go:
I use Zend's autoloader to load classes. It seems to work, at least it loads the correct file when instantiating my class (Common_TestTest) which is implemented in Common/TestTest.php. But then I get the following error message:
"Class Common_TestTest could not be found in Common/TestTest.php."
There's nothing in TestTest.php other than the class:
<?php
class Common_TestTest extends PHPUnit_Framework_TestCase
{
public function testTesting() {
$this->assertTrue(true);
$this->assertFalse(true);
}
}
I tried dumping get_declared_classes at the end of the file, everything looks fine, Common_TestTest is one of the declared classes - but the exception is still thrown when leaving the file.
The funniest bit is: When I change the name of the class from Common_TestTest to TestTest the same things happens - only that the error message states the name of the missing class as "TestTest". So it definitely sees the class and reacts to it's presence.
There are 2 possibilities that come to my mind as to what causes the problem:
You have some case-mismatch between class-name and file-name, e.g. Testtest.php
When registering the Namespace you use "Common" instead of "Common_". Zend_Loader_Autoloader does not distinguish between PHP 5.3 style namespaces and ZF-style namespaces (rather Prefix). By appending the underscore you make sure, that your namespace is interpreted as a class-prefix rather than a real namespace.

PHPUnit: stub methods undefined

I must be missing something. I'm trying to stub methods on a class in PHPUnit, but when I invoke the method on the mock object, it tells me that method is undefined.
Example class to stub:
namespace MyApp;
class MyStubClass
{
public function mrMethod()
{
// doing stuff
}
}
To stub it, I write:
// specifying all getMock() args to disable calling of class __construct()
$stub = $this->getMock('MyStubClass', array(), array(), 'MockMyStubClass', false, false, false);
$stub->expects($this->any())
->method('mrMethod')
->will($this->returnValue('doing stuff'));
But upon invoking the stubbed method, I get an exception:
$stub->mrMethod();
//PHP Fatal error: Call to undefined method MockMyStubClass::mrMethod()
I'm using PHPUnit 3.4.3 with PHP 5.3.0.
Another small thing I noticed was that if specifying a namespace in the getMock() method results in a class loading exception because of a double namespace:
$stub = $this->getMock('MyApp\MyStubClass');
// Fatal error: Class 'MyApp\MyApp\MyStubClass' not found
That strikes me as rather odd (and getmock() will not accept a namespace with a leading backslash). The only thing I could think to cause that would may be because this class is
registered with an autoloader?
Any thoughts?
Answering my own question:
After quite a bit of frustration, I did manage to get things working. I'm not sure precisely what the issue was, but did discover a few things that might help others:
Make sure you're running the latest version of PHPUnit (3.4.6 as of this writing)
Use the fully-qualified namespace minus the first backslash.
$this->getMock('MyApp\Widgets\WidgetFactory');
Part of my problem was that PHPUnit was creating a stub class WidgetFactory that was not actually stubbing MyApp\Widgets\WidgetFactory. One would expect that an exception would occur if trying to stub a non-existent class, but it doesn't seem to happen with the namespace confusion.
Also, there is a question over here that suggests using the class alias method as follows:
class_alias('MyApp\Widgets\WidgetFactory', 'WidgetFactory');
$this->getMock('WidgetFactory');
While this did temporarily solve my problem, I would strongly advise against using it. class_alias() cannot be called twice for the same alias without raising an exception, which causes obvious problem if used in the setup() method, or as part of the stub generation.
I had a similar issue, my problem was that the path I was writing was something like MyApp\Widgets\WidgetFactory\MyStubClass while the class was something like this:
namespace MyApp;
class MyStubclass
{
public function mrMethod()
{
// doing stuff
}
}
So there wasn't the uppercase C in the name of the class in the path

Categories