There are two ways to assert the exception in Phpunit:
using annotation #expectedException
using method call $this->expectException()
I've tried both of them, they work fine and exactly the same.
Which is the correct way?
Are there any guidelines on which one should be used?
PS: When the exception is based on some condition and does not always happen then obviously the method should be used.
Using expectException() is considered best practice, see this article.
There's a few clear advantages for me on why I'd choose to use the method rather than the annotation.
In the annotation form, you have to use the full namespace to the class name for it to work:
#expectedException MyException // Not found unless it is within the current namespace
#expectedException \Some\Deep\Namespace\MyException // works
The alternative:
$this->expectException(MyException::class); // works, with a 'use' statement
This is more readable, more explicit, flexible (automated refactoring/renaming would be a doddle in most editors like PHPStorm), is less code to write, and is in line with the standard test method setup of the 3 phases in correct order, Arrange, Assert, Act. Lastly, the annotation internally would need to be parsed, and would only call the expectException method anyway. So it's going to be more efficient as well.
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.
Symfony2 docs say that I should use alias shortcut 'ByBundle:myEntity' for entity path:
$em->getRepository('ByBundle:myEntity');
But this string literal is not usefull - no refactoring, no fast and auto renaming of the entity class in IDE.
I use magic method ::class
$em->getRepository(\ByBundle\Entity\myEntity::class);
The Question: am I doing this right?
In fact the Symfony2 core team is using the ::class method for example for adding form field types like so: $builder->add('name',TextType::class,array(...)) since Symfony v2.8 i guess there's nothing wrong the way you do it.
UPDATE:
This allows your IDE to throw an exception if the linked entity class namespace would change and you will be able to recognize this while the development process. That is definitely a "it's better to be safe than sorry" way of how to map the entity instead of having the full qualified namespace or even the alias as a string.
Yes. Using class reference is always better than string refence.
Using string usually means there is algorithm, that converts string to the class.
IDE friendly is also desired.
I have a number of methods being declared in a class with the standard phpdoc #method syntax, for example:
/**
* #method string magicMethod(int $arg1, array $arg2) Method description.
*/
class ... { }
Is it possible to configure PHPUnit to check against these comments when determining method-level code coverage? Currently, my coverage is at 100%, even though I've only touched about 10% of these magic methods so far.
Code coverage can only be calculated based on existing code, not on "virtual" methods.
To get a more realistic statistic, you should reduce the amount of coverage that gets generated unintentionally. PHPUnit does generate coverage for every line of code that gets executed when using the default configuration - which is bad, because if you unintentionally run along lines that do not get tested with an assertion, the coverage does not tell you anything at all (apart from the fact that no error occurred there).
When you have a look at the code coverage chapter in the manual, you see that you can specify which methods are tested with a test, and only those methods are generating coverage statistics (section "Specifying covered methods").
The method I prefer is to set the option mapTestClassNameToCoveredClassName="true" in the phpunit.xml file, and add all classes to be tested to the whitelist. That way, the coverage will automatically be restricted to only the class that has the same name (less the suffix "Test") as the test class. So if you have a test "MyGreatModelTest", it will only create coverage in any method of the "MyGreatModel" class, and not anywhere else.
And if you add the whole directory with your code to the whitelist, you will also catch all files that did generate 0 % coverage and thus were not included in the statistics so far.
Beware: These settings might hurt your feelings, but they will give you a more realistic picture of which lines of code get really run during a test, and which are only passed as a side effect.
PHPUnit uses its own annotations applied to your TestCase class. It doesn't parse annotations on tested class.
To restrict the source code lines used during code coverage analysis for a specific test, you have to use the #covers annotation.
In case of using magic methods in the tested class:
/**
* #covers My\Class::__call
*/
public function testMyMagicMethod()
{
$this->assertSomething($this->subject->magicMethod());
}
As __call() is the real method called in you tested class, the covered
source code lines should be in it.
I know this is super old, but the answer you're looking for is to mock the __call method.
$this->clientMock->expects(static::at(1))
->method('__call')
->with('get', [RedisAdapter::CONNECTION_TEST])
->will(static::returnValue(RedisAdapter::CONNECTION_TEST_VALUE));
Which action/method is in PHPUnit equal to in simpleTest:
$this->UnitTestCase('message .....')
Edit: Sorry for my mistake
I think what I asking about not exist in simple test directly its just our extended class.
But this method display message in begining of test- how its done with PHPUnit?
Thanks
I'm not a SimpleTest expert, but as far as I can tell that's the constructor for the UnitTestCase class. The equivalent in PHPUnit is PHPUnit_Framework_TestCase; you create your own tests by subclassing that and defining test methods. See the PHPUnit docs on writing tests for a quick howto and more info, but briefly, this is a complete PHPUnit test:
class MyTest extends PHPUnit_Framework_TestCase {
public function testSomething {
$this->assertTrue(MyClass::getSomethingTrue());
}
}
Update: to answer the revised question, the primary way to display messages in PHPUnit is on assertion failure. Every assert* function comes with an optional $message argument at the end, which you can use to display a custom message when that assertion fails.
If you want to always display a message, without having to fail an assertion, then you might try a straightforward print statement. It'll be interspersed with the test output, so this may not be the best (or nicest-looking) way of accomplishing what you want, but it'll certainly dump text to a console, which is what you seem to be asking.
If you're looking for some advanced debugging during unit-testing, you may also want to consider a logging framework of some kind (or even just a custom function that opens a file, prints to it, and closes the file again). That way, you preserve the integrity of the test output, but still get extra custom messages wherever you want them during your tests.