I like to test (Feature test via phpunit) some methods in the Job Class (Lumen Queue) to make sure it is working correctly.
Problem is Job Class have some jobs methods like $this->job->getJobId() and $this->release(5);
If I run phpunit from console, I get an error:
Error: Call to a member function getJobId() on null
The test code look like this:
/**
* #test
*/
public function it_has_successfully_uploaded()
{
$job = new SomeJob(['file' => ['file1.zip']]);
$job->handle();
}
How do I solve this issue?
Your code is using $this->job->getJobId(), but nowhere is that property declared or set. Are you perhaps using the InteractsWithQueue trait but forgot to include that in your code paste?
If so, the job property is set in InteractsWithQueue::setJob. This method may be called from several places, but in your case it is probably from Illuminate\Queue\CallQueuedHandler::setJobInstanceIfNecessary. This is some internal initialization of your job that Laravel does for you, which you must imitate in your test setup.
I would implement a simplistic version of Illuminate\Contracts\Queue\Job and call $job->setJob(new SimplisticVersionShazaamJob(...)); before calling $job->handle().
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've run into an issue with Lumen v5.0.10 that has me at my wits end. I'm designing an application largely using TDD with the bundled phpunit extensions. I'm basically getting a BindingResolutionException for "App\Contracts\SubscriberInteractionInterface". This is an interface in the directory App\Contracts which has an implementation in App\Services which is registered in the AppServiceProvider like so:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
// Owner manager
$this->app->singleton(
'App\Contracts\OwnerInteractionInterface',
'App\Services\OwnerManager'
);
// Subscriber manager
$this->app->singleton(
'App\Contracts\SubscriberInteractionInterface',
'App\Services\SubscriberManager'
);
// dd($this->app->bound('App\Contracts\SubscriberInteractionInterface'));
}
}
My frustration is that if I uncomment that last line in the function then it shows that App\Contracts\SubscriberInteractionInterface has been bound (and thus may be resolved).
I then have a controller which effectively looks like this
class MyController extends Controller {
public function __construct(LoggerInterface $log)
{
$this->log = $log;
}
public function index(Request $request)
{
if (/* Seems to come from owner */)
{
$owners = App::make('App\Contracts\OwnerInteractionInterface');
return $owners->process($request);
}
if (/* Seems to come from subscriber */)
{
$subscribers = App::make('App\Contracts\SubscriberInteractionInterface');
return $subscribers->process($request);
}
}
}
I use them in this way because I only want the relevant one instantiated (not both as would happen if I type-hinted them) and they also each have type hinted dependencies in their constructors.
The issue is that the route of the tests that needs OwnerInteractionInterface runs just fine but the one that needs SubscriberInteractionInterface does not. The implementations and interfaces are largely similar and as I showed before, they are both registered at the same time and I can confirm that SubscriberInteractionInterface is bound. In fact, if I put the line:
dd(App::bound('App\Contracts\SubscriberInteractionInterface'));
at the top of index() it returns true. The tests happen to be ordered such that the path that uses OwnerInteractionInterface runs first and it resolves fine and then the other test fails with a BindingResolutionException. However, if I omit other tests and run just that one, then everything goes smoothly. The tests are in different files and the only setup I do is to bind a mock for a third party API in place of an entirely different binding from those shown and none of that code touches these classes. This is done within a setUp() function and I make sure to call parent::setUp() within it.
What's going on here? Could it be that binding one concrete instance wipes non-concrete bindings from the IoC? Or is it that the default setup allows some influence to transfer over from one test to another?
I know I sorta have a workaround but the constraint of never running the full test-suite is annoying. Its starting to seem that testing would be easier if I just use the instance directly instead of resolving it from its interface.
Also, does anyone know a way to inspect the IoC for resolvable bindings?
After further attempts at debugging, I've found that if you use app(...) in place of App::make(...) then the issue does not come up. I put in a eval(\Psy\sh()) call in the tearDown of the TestCase class and found that after a few tests you get the following result:
>>> app()->bound('App\Contracts\OwnerInteractionInterface')
=> true
>>> App::bound('App\Contracts\OwnerInteractionInterface')
=> false
>>> App::getFacadeRoot() == app()
=> false
This is to say that somehow, the Laravel\Lumen\Application instance that the App facade uses to resolve your objects is not the same as the current instance that is created by the setUp() method. I think that this instance is the old one from which all bindings have been cleared by a $this->app->flush() call in the tearDown() method so that it can't resolve any custom bindings in any tests that follow the first tearDown() call.
I've tried to hunt down the issue but for now I have to conclude this project with this workaround. I'll update this answer should I find the actual cause.
Instead of use bind, you can use bindIf method. Container will check whether the abstract has been bound or not. If not, it will bind your abstract and vice versa. You can read the api here.
So if you use singleton, you may use bindIf like.
// Owner manager
$this->app->bindIf(
'App\Contracts\OwnerInteractionInterface',
'App\Services\OwnerManager',
true
);
// Subscriber manager
$this->app->bindIf(
'App\Contracts\SubscriberInteractionInterface',
'App\Services\SubscriberManager',
true
);
I have a line of code in a Laravel 5 event handler which looks like this:
$this->event->batch->increment('attempted_jobs');
$this->event is the event which calls the handler and $this->event->batch contains my Batch model. All this does in increment the attempted_jobs column within my database, so it's fairly basic stuff.
I would like to be able to test this event handler, I'm using Codeception and Mockery. My mock for $this->event->batch looks like this:
$batch = m::mock('MyVendor\MyApp\Batch');
$batch->shouldReceive('increment')->once()->with('attempted_jobs');
This however causes issues - increment() is a protected method of Model and therefore cannot be mocked. Here's the exact error:
InvalidArgumentException: increment() cannot be mocked as it a protected method and mocking protected methods is not allowed for this mock
It appears to be implemented using the __call() PHP magic method, so how to I mock this? I've attempted creating a __call() mock, but this churns out tonnes of errors related to the increment() method not being implemented.
The issue was because, as stated, increment() is a protected method of Illuminate\Database\Eloquent\Model(). The way to get around this is to mock the __call() method directly, like so:
$batch = m::mock('MyVendor\MyApp\Batch');
$batch->shouldReceive('__call')->with('increment')->once();
(I'm not sure why this didn't work when I first tried it though)
In a Yii project, I am using phpunit with getMockbuilder. When I run unit tests on the whole file, they all pass. However, when I do phpunit --filter testMyFunction, I get the following error. "Call to undefined method Mock_Account_3a811374::__construct() ..."
After doing a little more checking I see that if the --filter ends up including a test that does not use the mock in addition to the one that does, then it works fine.
Anyone have any ideas on how to fix it?
Here's some of my code (simplified) ...
use components\Account;
class UtilsTest extends CDbTestCase
{
...
public function testMyFunction()
{
$accountStub = $this->getMockBuilder('Account')
->disableOriginalConstructor()
->setMethods(array('methodToStub'))
->getMock();
$accountStub->expects($this->any())
->method('methodToStub')
->will($this->returnValue(false));
$accountStub->__construct();
...
}
}
I'm confused by what are are trying to do with $accountStub->__construct() when you have specified that you don't want to call the original constructor with ->disableOriginalConstructor()?
You can call the constructor yourself and pass parameters with setConstructorArgs(array $args). So this would look something like this:
$accountStub = $this->getMockBuilder('Account')
->setConstructorArgs($args)
->getMock();
However it wouldn't make sense to call this at the same time as disableOriginalConstructor(). I think you probably want to do one or the other.
I believe the error message you are getting is just PHP telling you that you are trying to do something that doesn't make sense. You are trying to call the constructor on an object that has already been constructed. What's more, you have even specifically told the mock to skip the call to the constructor method. PHP is just telling you that this method does not exist.
I think you probably just need to remove the following line and test again:
$accountStub->__construct();
class MyClass {
private $numeric;
public function MyMethod($numeric)
{
if (! is_numeric($numeric)) {
throw new InvalidArgumentException
}
$this->numeric = $numeric;
}
}
1 - It is necessary to test whether the class exists?
The PHPUnit has several methods that run automatically, such as assertPreConditions, setUp, and others. It is necessary within these methods check whether the class exists using the assertTrue and class_exists? Example:
protected function assertPreConditions()
{
$this->assertTrue(class_exists("MyClass"), "The class does not exists.");
}
2 - It is necessary to check if a method exist? If yes, this test should
be a separate test or within each unit test?
Suppose we have a method that accepts only numeric type parameters, so we have two tests, one test with the correct parameter and another with incorrect method expecting an exception, right? The correct way to write this method would be ...
This way:
public function testIfMyMethodExists()
{
$this->assertTrue(method_exists($MyInstance, "MyMethod"), "The method does not exists.");
}
/**
* #depends testIfMyMethodExists
* #expectedException InvalidArgumentExcepiton
*/
public function testMyMethodWithAValidArgument()
{
//[...]
}
/**
* #depends testIfMyMethodExists
* #expectedException InvalidArgumentExcepiton
*/
public function testMyMethodWithAnInvalidArgument()
{
//[...]
}
Or this way?
public function testMyMethodWithAValidArgument()
{
$this->assertTrue(method_exists($MyInstance, "MyMethod"), "The method does not exists.");
}
/**
* #expectedException InvalidArgumentExcepiton
*/
public function testMyMethodWithAnInvalidArgument()
{
$this->assertTrue(method_exists($MyInstance, "MyMethod"), "The method does not exists.");
//[...]
}
And why?
3 - What is the real purpose of the #covers and the #coversNothing?
I was reading a document that Sebastian Bergmann the creator of PHPUnit wrote as good practice we should always write #covers and #coversNothing in the methods and class and add these options in the xml:
mapTestClassNameToCoveredClassName="true"
forceCoversAnnotation="true"
and in the whitelist:
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php"></directory>
</whitelist>
But what is the real need for it?
4 - What is the correct way to test a constructor that calls another method?
It seems everything is fine, but not on tests.
Even if I do tests with valid arguments and invalid arguments expecting an exception on the method "MyMethod", that will not happen if I enter an incorrect value in the constructor (The test fails).
And if I test with valid argument, the code coverage does not result in 100%.
public function __construct($numeric)
{
$this->MyMethod($numeric);
}
I write tests for methods that should exist, to test that the code does what it is expected of it. I also test the InstanceOf() the class (and inherited class definitions) to ensure the object did create what was suppose to be created. If FOO() extends BAR(), then I test that my object created is an InstanceOf(FOO) and InstanceOf(BAR). If the class gets changed to inherit from something else, or has the extends removed, my test will once again inform the developer to check the code to ensure this change is desired. Potentially some inherited functions are being called on FOO, and without the extends from BAR, this code will break.
I write tests on the various code paths that are there to be executed. Therefore, if I expect that the function should throw an exception when bad data is passed, I write a test for that. I do this to also help document our source code. The tests show the expected behavior. If someone removes the exception (new functionality to accept a different parameter type) then the test should be updated to show that this is allowed. Potentially, the change to the parameters could cause a problem elsewhere. Testing this way ensures that years later, I know that code required this to be a number, and I should definitely re-factor the code carefully if I am changing the parameter types.
Using Test Driven Development (TDD) may cause you to not write the code to throw the exception, as you write a test, then the code to make the test pass. As such, you might not be testing all the parameters and their types or values, but I try to do this as best as I can to validate reasonable data input to try to avoid the Garbage In/Garbage Out (GIGO) problem.
All these tests also give me a good code coverage metric, as the majority of the code base is tested, and the code does step through all the lines in the class files. However, testing to this level, and trying to achieve a high code coverage metric, is really a choice for your team to make if this is desired or not.
I can't see any reason nor advantage of writing this kind of tests. If the class or the method doesn't exist your tests will fail anyway. So you don't have any profit from writing them.
Maybe the one exception from above (point 1) could be a situation where you always build SUT using PHPUnit mock framework (theoretically a situation where your SUT is a mock with mocked other method you don't need in particular test is possible, but I can't imagine real situation which leads to it).
In my opinion if the test covers flow path which is completely included in another test - this means that the test is redundant and unnecessary.
edit:
ad 3. Because you want to know which exactly flow path was invoked by particular unit test. Let's say you have two methods A and B. Method B invokes method A. If you don't have #covers annotation test for method B could generate code coverage for method A, and you can't say if your unit tests for A covers code in 100%.