I have a trait with two tests, each of which has a separate data provider:
trait StringTest
{
/**
* #dataProvider stringProvider
*/
public function testString($expression, $string)
{
//echo $name;
$this->assertInternalType('string', $string, "Not a string.");
$this->assertRegExp($expression, $string);
}
/**
* #dataProvider nonmatchingStringProvider
*/
public function testNonmatchingString($expression, $string)
{
//echo $name;
$this->assertInternalType('string', $string, "Not a string.");
$this->assertNotRegExp($expression, $string);
}
}
I have test classes that have cases to test with testString but not with nonmatchingStringProvider. I want to ignore testNonmatchingString altogether when I don't have any cases for nonmatchingStringProvider. Ignore completely. Not report any sort of error, warning, or skip. (In this case the developer accepted a solution that marks the test as skipped. A skip is not acceptable here.)
I could break the trait into two separate traits. I would prefer not to do that.
Is it possible to code or configure phpunit to behave this way, either by omitting nonmatchingStringProvider in the class or defining nonmatchingStringProvider and having it return an empty array?
Related
I'm currently following a Symfony tutorial, and I've gotten to the part of Doctrine bidirectional relations (sorry if the terms I'm using are wrong, I'm not an native English speaker). My model is based on an Advert (One-To-Many) that displays an array of Applications (Many-To-One) made to this Advert. So an Application has to be linked to an Advert, hence the nullable false :
class Application
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Advert", inversedBy="applications")
* #ORM\JoinColumn(nullable=false)
*/
private $advert;
//
}
And I added an $applications attribute to my Advert class:
class Advert
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Application", mappedBy="advert")
*/
private $applications;
//
}
But when I use php bin/console make:entity --regenerate, to get the removeApplication() function, the code I'm getting is the following:
public function removeApplication(Application $application): self
{
if ($this->applications->contains($application)) {
$this->applications->removeElement($application);
// set the owning side to null (unless already changed)
if ($application->getAdvert() === $this) {
$application->setAdvert(null);
}
}
return $this;
}
The function sets the $advert of the Application to null while this attribute is explicitly set to nullable = false. I noticed this inconsistency because I'm using Symfony 4 whereas the tutorial I'm following is based on an older version, so the functions generated in the tutorial were much simpler and did not handle the $advert attribute.
Any idea why this is happening and if it might cause an error later in my project? Let me know if you need more pieces of code to understand the problem.
That really looks like a bug to me, they probably do not handle nullable cases inside of the generator.
Maybe try orphanRemoval on the Advert side of the relation, would be interesting what would happen then:
class Advert
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Application", mappedBy="advert", orphanRemoval=true)
*/
private $applications;
}
I've just started working with Doctrine and built a simple blog project. One of my requirements is that a blog post should not be visible to anybody (for simpleness, skip an editor's interface) until the publish date is reached.
As far as I see, it's obvious to do so using a custom repository. Let's extend the find method the following way:
public function find($id, $lockMode = null, $lockVersion = null)
{
/** #var Post $post */
$post = parent::find($id, $lockMode, $lockVersion);
if($post->getCreatedAt() > new \DateTime()) {
return null;
}
return $post;
}
This restricts the access for a page showing a single Post entity. For an overview page, the same can be done using a custom method:
public function findForOverview()
{
$query = $this->createQueryBuilder('p')
->where('p.createdAt < CURRENT_TIMESTAMP()')
->orderBy('p.createdAt', 'DESC')
->getQuery();
return $query->getResult();
}
So, even for this simple requirement, I've already written two custom methods. If I continue to work on my project, other restriction limitations might occur and additional ways to load that entity might arise. And as far as I see, for each case I have to implement the logic for all access guards.
Is there no simpler way to do that? I'm thinking of something like an annotation or an "entity load listener" that makes it simple to write one single entry point for all such checks - making it impossible to forget such checks...
Such restrictions are usually implemented by using mechanism of SQL filters in Doctrine. Implementation of this filter works on lower level then DQL and allows you to apply modifications for SQL query being constructed. In your case it may look like this:
namespace App\ORM\Filter;
use App\Entity\Post;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class PostVisibilityFilter extends SQLFilter
{
/**
* Gets the SQL query part to add to a query.
*
* #param ClassMetadata $targetEntity
* #param string $targetTableAlias
* #return string The constraint SQL if there is available, empty string otherwise
*/
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
if ($targetEntity->name !== Post::class) {
return '';
}
return sprintf('%s.%s >= now()', $targetTableAlias, $targetEntity->getColumnName('createdAt'));
}
}
Today, I've tried to upgrade my project to the new version of Symfony (3.3), and I'm encountering a problem with my mocks.
Until today, I was doing my mocks like this:
$client = $this->makeClient();
$mockObject = new \stdClass();
$mock = $this->getMockBuilder('SomeClass')
->disableOriginalConstructor()
->setMethods(['method1', 'method2'])
->getMock();
$mock->expects($this->once())
->method('method1')
->will($this->returnValue($mockObject));
$client->getContainer()->set('my_service', $mock);
Here, method1 is just a Guzzle post, nothing else.
Now, I'm getting the following error:
Setting the "my_service" pre-defined service is deprecated since Symfony 3.3 and won't be supported anymore in Symfony 4.0: 1x
After some research, it seems that I cannot use the last line of my code.
Problem is, I can't see nor find any solution to solve this deprecation.
There's few ways of solving your problem.
TestDoubleBundle
TestDoubleBundle makes it easier to create test doubles. You can use dependency injection tags to automatically replace a service with either a stub or a fake.
Override the container
Another way is to extend the container in the test environment, so it allows stubbing. Here's a draft of the idea:
<?php
namespace Zalas\Test\DependencyInjection;
use Symfony\Component\DependencyInjection\Container;
class MockerContainer extends Container
{
/**
* #var object[] $stubs
*/
private $stubs = array();
public function stub($serviceId, $stub)
{
if (!$this->has($serviceId)) {
throw new \InvalidArgumentException(sprintf('Cannot stub a non-existent service: "%s"', $serviceId));
}
$this->stubs[$serviceId] = $stub;
}
/**
* #param string $id
* #param integer $invalidBehavior
*
* #return object
*/
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
{
if (array_key_exists($id, $this->stubs)) {
return $this->stubs[$id];
}
return parent::get($id, $invalidBehavior);
}
/**
* #param string $id
*
* #return boolean
*/
public function has($id)
{
if (array_key_exists($id, $this->stubs)) {
return true;
}
return parent::has($id);
}
}
To enable this container you'll need to override the getContainerBaseClass method in your AppKernel:
/**
* #return string
*/
protected function getContainerBaseClass()
{
if ('test' == $this->environment) {
return '\Zalas\Test\DependencyInjection\MockerContainer';
}
return parent::getContainerBaseClass();
}
You might need to tweak the code a bit, perhaps declare MockerContainer::$stubs as static (although if your previous approach worked it shouldn't be needed - it might be needed if you need to stub for multiple requests).
Now you should be able to use the container to stub services like this:
$client->getContainer()->stub('my_service', $myServiceStub);
Use a synthetic service
Another way of working around your issue is defining your service as synthetic. You could write a compiler pass that would only mark the service as synthetic in a test environment.
Your question is also discussed here.
You can take a look at this osservation:
Note that you don't need to fix the deprecations when moving to 3.3.
But you will when moving to 4.0.
And this workaround:
Well, you can mark these tests as #legacy to avoid making them fail
temporarily, to give you time to migrate the tests, if that takes
time. This is the whole point of deprecations: you can migrate
progressively (I also prefer removing deprecations as fast as
possible, but for some of them, it may require more time)
I find it somewhat annoying to have to constantly use #var on getUser. It seems sloppy.
So I was thinking about starting to use this instead
<?php
// in the controller
$user = Customer::isCustomer($this->getUser());
// in the entity
/**
* #param Customer $user
*
* #return Customer
*/
public static function isCustomer(Customer $user)
{
return $user;
}
Is this a good idea? Bad idea? Horrible idea?
A type hint is the better option in this case.
Why would you write more code by adding checks manually rather than adding a simple type hint to your param.
Your four lines of codes representing two conditions give exactly the same result as:
/**
* #param Customer|null $user
*
* #return Customer|null
*/
public static function isCustomer(Customer $user = null)
{
// If $user is null, it works
// If $user is a Customer instance, it works
// If it's other, an exception is thrown
return $user;
}
Type hinting optimises and give more readability to a code.
It's a convention in symfony2, php and more.
It's commonly used as a constraint (or contract) with you and your method.
Also, it's the only alternative for an interface or an abstract class to add requirement to a parameter, because they don't have a body, and so cannot write conditions.
Update
In SensioLabs Insight, Object type hinting represents a warning using the following message :
The parameter user, which is an object, should be typehinted.
Because the verb should is used, I consider it's not a mandatory requirement, just a very good practice in case of it doesn't cause any problem.
Also, you can use the example you given without making your code horrible.
When I try to execute phpUnit with multiple test, someone fails with this output:
No matching handler found [...] Either the method was unexpected or its arguments matched no expected argument list for this method.
But if I execute the test alone, pass without errors. How I force to use different Mockery object in each test?
EDITED:
If I run phpunit separately, works fine and all test passed, but if I use the directory which contains the 2 classes... the 1st method passed but the second throw me this exception.
I have this method in the class AddCountryTest:
public function testAddCountry()
{
$name = 'test testAddCountryCommand';
$iso = 'derd';
$telPrefix = '+34 5';
$languageId = 1;
/* #var WtsCqrsRequest $wtsCqrsRequest */
$wtsCqrsRequest = \Mockery::mock('Wts\Country\App\Cqrs\Country\Request\AddCountryRequest')->makePartial();
/** #noinspection PhpUndefinedMethodInspection */
$wtsCqrsRequest
->shouldReceive('getIso')->andReturn($iso)
->shouldReceive('getTelPrefix')->andReturn($telPrefix)
->shouldReceive('getName')->andReturn($name)
->shouldReceive('getDefaultLanguageId')->andReturn($languageId)
;
$commandHandler = $this->getCommandHandler($wtsCqrsRequest);
/* #var Country $country */
$country = $commandHandler->handle($wtsCqrsRequest);
$this->assertEquals($country->getIso(), $iso);
}
and this one in the class ReadCountryTest:
public function testReadCountry()
{
$name = 'test testAddCountryCommand';
$iso = 'zzz';
$telPrefix = '+34 5';
$languageId = 1;
$this->createCountry($name,$iso,$telPrefix,$languageId);
$iso = 'aaa';
$this->createCountry($name,$iso,$telPrefix,$languageId);
$iso = 'vvv';
$this->createCountry($name,$iso,$telPrefix,$languageId);
//Read
/* #var WtsCqrsRequest $wtsCqrsRequest */
$wtsCqrsRequest = \Mockery::mock('Wts\Country\App\Cqrs\Country\Request\ReadCountriesRequest')->makePartial();
$commandHandler = $this->getCommandHandler($wtsCqrsRequest);
/** #var WtsCqrsResponse $wtsCqrsResponse */
$wtsCqrsResponse = $commandHandler->handle($wtsCqrsRequest);
/* #var CountryDto[] countries */
$countries = $wtsCqrsResponse->getData();
/** #var CountryDto $countryRead */
$this->assertEquals(count($countries), 3);
}
Thanks
I had the same problem and I know this question is almost 7 years old but it's the first result that appeared on Google, so...
If the individual test works but if several of them run at the same time fails it is because you need to either manually reset Mockery or to implement the MockeryTestCase,
http://docs.mockery.io/en/latest/reference/phpunit_integration.html
After that, all the tests would work as expected.