Laravel Unit Testing Dependency Injection - php

I'm trying to write a test class for a shopping cart. Here is what I have:
ShoppingCartTest.php
class ShoppingCartTest extends TestCase {
use DatabaseTransactions;
protected $shoppingCart;
public function __construct() {
$this->shoppingCart = resolve('App\Classes\Billing\ShoppingCart');
}
/** #test */
public function a_product_can_be_added_to_and_retrieved_from_the_shopping_cart() {
// just a placeholder at the moment
$this->assertTrue(true);
}
}
However, when I run phpunit, it seems like Laravel is unable to resolve my ShoppingCartClass.
Here is the error:
Fatal error: Uncaught exception 'Illuminate\Contracts\Container\BindingResolutionException'
with message 'Unresolvable dependency resolving
[Parameter #0 [ <required> $app ]] in class Illuminate\Support\Manager'
in C:\Development Server\EasyPHP-Devserver-16.1\eds-www\nrponline\vendor\laravel\framework\src\Illuminate\Container\Container.php:850
I have my ShoppingCart class being resolved in a number of different controllers just fine.
Why can't Laravel resolve it during my tests?
I refered to this post as well but still didn't have any luck.

I figured it out. Here is the updated class.
class ShoppingCartTest extends TestCase {
use DatabaseTransactions;
protected $shoppingCart;
public function setUp() {
parent::setUp();
$this->shoppingCart = $this->app->make('App\Classes\Billing\ShoppingCart');
}
/** #test */
public function a_product_can_be_added_to_and_retrieved_from_the_shopping_cart() {
// just a placeholder at the moment
$this->assertTrue(true);
}
}
Thanks to #edcs for guiding me in the right direction.
You need to use a setUp function and not __construct as the app instance hasn't been created yet.

If you want to use __construct you have to use the same constructor of PHPUnit\Framework\TestCase and remember to call the parent method if you don't want to break anything
class MyTest extends TestCase
{
public function __construct($name = null, array $data = [], $dataName = '')
{
parent::__construct($name, $data, $dataName);
// my init code
}
}
However the proper way would be to use the method setUpBeforeClass() if you want to execute your init code once or setUp() if you want to execute the init code before each test contained in your class.
Check PHPUnit documentation for more details.

Related

Factory helper not working within PHPUnit setup method

Writing some unit tests, and I want to have an object created before the tests in the class are done. So I set up the setUpBeforeClass() method:
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Location;
class UserTests extends TestCase {
const FAKEID = 9999999;
public static function setUpBeforeClass() : void {
parent::setUpBeforeClass();
factory(Location::class)->make(["id" => self::FAKEID])->save();
}
}
But when I try running this, I get this error:
InvalidArgumentException: Unable to locate factory with name [default] [App\Location].
But the factory class is set up properly. In fact, if I move this same line down to one of my test functions it works perfectly.
public function testCreateUser() {
factory(Location::class)->make(["id" => self::FAKEID])->save();
// do other stuff...
}
The only thing that sticks out to me as different about setUpBeforeClass() is that it's a static method, but I don't know why that would prevent the factory class from working.
Laravel does a lot of setting up in the setUp() method in the TestCase class. The setUpBeforeClass() method is called before that, that's why your factory is not loaded yet.
The Laravel's TestCase class setup method (see class):
/**
* Setup the test environment.
*
* #return void
*/
protected function setUp()
{
if (! $this->app) {
$this->refreshApplication();
}
$this->setUpTraits();
foreach ($this->afterApplicationCreatedCallbacks as $callback) {
call_user_func($callback);
}
Facade::clearResolvedInstances();
Model::setEventDispatcher($this->app['events']);
$this->setUpHasRun = true;
}
Change your code to use setUp instead:
protected static function setUp() : void
{
parent::setUp();
factory( Location::class )->make( ["id" => self::FAKEID] )->save();
}

Laravel integration testing jobs

I am trying to run an integration tests for my app. I have those jobs:
StartJob
PrepareJob
PeformJob
StartJob dispatches one or more PrepareJob, every PrepareJob dispatches one PerformJob.
Adding this
$this->expectsJobs(
[
StartJobs::class,
PrepareJob::class,
PerformJob::class
]
);
makes my test fail with error saying
1) JobsTest::testJobs
BadMethodCallException: Method Mockery_0_Illuminate_Contracts_Bus_Dispatcher::dispatchNow() does not exist on this mock object
Removing $this->expectsJobs makes all my tests pass, but I can't assert a given job was run, only whether it modified the DB to a given state.
StartJobs.php
class StartJobs extends Job implements ShouldQueue
{
use InteractsWithQueue;
use DispatchesJobs;
public function handle(Writer $writer)
{
$writer->info("[StartJob] Started");
for($i=0; $i < 5; $i++)
{
$this->dispatch(new PrepareJob());
}
$this->delete();
}
}
PrepareJob.php
class PrepareJob extends Job implements ShouldQueue
{
use InteractsWithQueue;
use DispatchesJobs;
public function handle(Writer $writer)
{
$writer->info("[PrepareJob] Started");
$this->dispatch(new PerformJob());
$this->delete();
}
}
PerformJob.php
class PerformJob extends Job implements ShouldQueue
{
use InteractsWithQueue;
public function handle(Writer $writer)
{
$writer->info("[PerformJob] Started");
$this->delete();
}
}
JobsTest.php
class JobsTest extends TestCase
{
/**
* #var Dispatcher
*/
protected $dispatcher;
protected function setUp()
{
parent::setUp();
$this->dispatcher = $this->app->make(Dispatcher::class);
}
public function testJobs()
{
$this->expectsJobs(
[
StartJobs::class,
PrepareJob::class,
PerformJob::class
]
);
$this->dispatcher->dispatch(new StartJobs());
}
}
I think it has to do something with how I am using a concrete dispatcher, while $this->expectsJob mocks the dispatcher. Might be related to this - https://github.com/laravel/lumen-framework/issues/207. What's the way to solve this?
To me it sounds like there is no dispatchNow()-method. In the Jobs your run dispatch() but the error says dispatchNow() does not exist.
Laravel didn't have the dispatchNow()-method before a certain version (i think Laravel 5.2 ... not sure) but just the dispatch(). Could be that the expectsJobs didn't think about that and fails.
You could try not passing it in one array but use 3 commands:
$this->expectsJobs(StartJobs::class);
$this->expectsJobs(PrepareJob::class);
$this->expectsJobs(PerformJob::class);
Maybe that helps.

Transactions doesn't work on some tests

I'm working with Laravel 5.2 and phpunit and i'm writing some test for my application. until now i got no problem, and today i encounter something weird and I can't find a way to handle it.
Some of my test file doesn't use transactions although the others does.
i use use DatabaseTransactions; in my TestCase class with is extended in every test file i got.
Most of my test works without any troubles but some of them does not.
Here is one which works withotut any troubles :
class V2LikeTest extends TestCase {
protected $appToken;
protected $headers;
public function setUp() {
parent::setUp();
$this->generateTopic(1);
}
/** LIKE TOPICS */
/** #test */
public function it_likes_a_topic() {
$job = new ToggleLikeJob(Topic::first());
$job->handle();
$topic = Topic::first();
$this->assertTrue($topic->present()->isLiked);
$this->assertEquals(1, $topic->nb_likes);
}
}
and this one with troubles:
class V2TopicTest extends TestCase {
private $appToken;
private $headers;
public function setUp() {
parent::setUp();
$this->generateCompany(1);
}
/** #test */
public function it_create_a_topic() {
$new_topic_request = new Request([
'content' => str_random(100),
'type' => Topic::TYPE_FEED_TEXT
]);
$job = new CreateFeedTopicJob($new_topic_request->all());
$job->handle();
$this->assertCount(1, Topic::all());
}
}
It's been a while now that i'm looking for the solution but not able to find it. Did someone already meet this troubles?
edit: GenerateTopic function use generateCompany just in case ;)

Laravel Testing Service Dependency Injection Error

To start from the conclusion, I get this error:
[ErrorException]
Argument 1 passed to SomeValidatorTest::__construct() must be an instance of App\Services\Validators\SomeValidator, none given, called in ....vendor/phpunit/phpunit/src/Framework/TestSuite.php on line 475 and defined
In Laravel app, I have a script called "SomeValidator.php" which looks like this:
<?php namespace App\Services\Validators;
use App\Services\SomeDependency;
class SomeValidator implements ValidatorInterface
{
public function __construct(SomeDependency $someDependency)
{
$this->dependency = $someDependency;
}
public function someMethod($uid)
{
return $this->someOtherMethod($uid);
}
}
which runs without error.
Then the test script, SomeValidatorTest.php looks like this:
<?php
use App\Services\Validators\SomeValidator;
class SomeValidatorTest extends TestCase
{
public function __construct(SomeValidator $validator)
{
$this->validator = $validator;
}
public function testBasicExample()
{
$result = $this->validator->doSomething();
}
}
The error shows up only when the test script is ran through './vendor/bin/phpunit' The test class seems to be initiated without the dependency stated and throws an error. Does anyone know how to fix this? Thanks in advance.
You cannot inject classes into the tests (as far as i know), given that they are not resolved automatically by laravel/phpUnit.
The correct way is to make (resolve) them through laravel's app facade. Your test script should look like this:
<?php
class SomeValidatorTest extends TestCase
{
public function __construct()
{
$this->validator = \App::make('App\Services\Validators\SomeValidator');
}
public function testBasicExample()
{
$result = $this->validator->doSomething();
}
}
Source: http://laravel.com/docs/5.1/container

phpunit w/ laravel: missing argument when using providers

I'm running PHPUnit w/ Laravel. Here's my test:
class UserTest extends TestCase {
public function testSluggifyReturnsSluggifiedString($originalString, $expectedResult)
{
$url = new User();
$result = $url->sluggify($originalString);
$this->assertEquals($expectedResult, $result);
}
public function providerTestSluggifyReturnsSluggifiedString()
{
return array(
array('This string will be sluggified', 'this-string-will-be-sluggified'),
array('THIS STRING WILL BE SLUGGIFIED', 'this-string-will-be-sluggified'),
array('This1 string2 will3 be 44 sluggified10', 'this1-string2-will3-be-44-sluggified10'),
array('This! #string#$ %$will ()be "sluggified', 'this-string-will-be-sluggified'),
array("Tänk efter nu – förr'n vi föser dig bort", 'tank-efter-nu-forrn-vi-foser-dig-bort'),
array('', '')
);
}
}
As I'm familiarizing myself w/ unit testing, I've simply added the sluggify() function to the bottom of the User class.
I get this error:
There was 1 error:
1) UserTest::testSluggifyReturnsSluggifiedString
ErrorException: Missing argument 1 for UserTest::testSluggifyReturnsSluggifiedString()
If I change the test to define the two arguments (public function testSluggifyReturnsSluggifiedString($originalString='test', $expectedResult='test')) The test runs fine. For some reason it's not reading the provider data.
I suspect this is something about the Laravel setup, but I can't find anything in the docs to point me in the right direction. What am I doing wrong?
From PHPUnit docs on Data Provider:
The data provider method to be used is specified using the #dataProvider annotation.
So you have to add the annotation on top of your test method, for example:
class UserTest extends TestCase {
/**
* #dataProvider providerTestSluggifyReturnsSluggifiedString
*/
public function testSluggifyReturnsSluggifiedString($originalString, $expectedResult)
{
// ...
}
public function providerTestSluggifyReturnsSluggifiedString()
{
// ...
}
}

Categories