Can anyone please provide a standard example for developing in Symfony2 using the TDD notation? Or share links to interesting materials for TDD Symfony2 development (except the official documentation :))?
P.S. Is anybody writing unit tests for controller part of MVC pattern?
I just did it for silex, which is a micro-framework based on Symfony2. From what I understand, it's very similar. I'd recommend it for a primer to the Symfony2-world.
I also used TDD to create this application, so what I did was:
I wrote my first test to verify the route/action
Then I implemented the route in my bootstrap
Then I added assertions to my test e.g., what should be displayed
I implemented that in my code and so on
An example testcase (in tests/ExampleTestCase.php) looks like this:
<?php
use Silex\WebTestCase;
use Symfony\Component\HttpFoundation\SessionStorage\ArraySessionStorage;
class ExampleTestCase extends WebTestCase
{
/**
* Necessary to make our application testable.
*
* #return Silex\Application
*/
public function createApplication()
{
return require __DIR__ . '/../bootstrap.php';
}
/**
* Override NativeSessionStorage
*
* #return void
*/
public function setUp()
{
parent::setUp();
$this->app['session.storage'] = $this->app->share(function () {
return new ArraySessionStorage();
});
}
/**
* Test / (home)
*
* #return void
*/
public function testHome()
{
$client = $this->createClient();
$crawler = $client->request('GET', '/');
$this->assertTrue($client->getResponse()->isOk());
}
}
my bootstrap.php:
<?php
require_once __DIR__ . '/vendor/silex.phar';
$app = new Silex\Application();
// load session extensions
$app->register(new Silex\Extension\SessionExtension());
$app->get('/home', function() use ($app) {
return "Hello World";
});
return $app;
My web/index.php:
<?php
$app = require './../bootstrap.php';
$app->run();
Related
We are migrating applications from CakePHP 2.X but we need to implement our mobile API's before the migration. I have followed all the items I could find but they all seem to be for v5 or less. No matter what I do Hash::make() still results in a Bcrypt password.
I really want to 2 birds one stone with having this allow sha1() login and update to Bcrypt upon login but we havent implemented on CakePHP 2.x successfully. So I need to get the Hasher working or a workaround. I know I can just Hash manually in the model but that doesnt allow Auth to work.
Any help would be appreciated
app.php config file
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
//Illuminate\Hashing\HashServiceProvider::class,
App\Providers\CustomHashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
CustomHashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Hashing\HashServiceProvider;
use App\Libs\CustomHash\CustomHasher as CustomHasher;
class CustomHashServiceProvider extends HashServiceProvider
{
public function register()
{
$this->app->singleton('hash', function () {
return new CustomHasher;
});
}
}
CustomHasher.php
<?php
namespace App\Lib\CustomHash;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
class CustomHasher implements HasherContract {
/**
* Hash the given value.
*
* #param string $value
* #return array $options
* #return string
*/
public function make($value, array $options = array()) {
//I have custom encoding / encryption here//
//Define your custom hashing logic here//
return sha1(env('SEC_SALT').$value);
}
/**
* Check the given plain value against a hash.
*
* #param string $value
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function check($value, $hashedValue, array $options = array()) {
return $this->make($value) === $hashedValue;
}
/**
* Check if the given hash has been hashed using the given options.
*
* #param string $hashedValue
* #param array $options
* #return bool
*/
public function needsRehash($hashedValue, array $options = array()) {
return false;
}
public function info($hashedValue): array {
return $hashedValue;
}
}
UPDATE
I refactored based on #Mdexp answer to this .... but I found out the Configs are ignored unless added in app.php on Lumen
New app.php
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\Sha1HashServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
Sha1HashServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class Sha1HashServiceProvider extends ServiceProvider {
public function register() {
//
}
public function boot() {
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new \App\Lib\Sha1Hash\Sha1Hasher;
});
}
}
Sha1Hasher.php
<?php
namespace App\Lib\Sha1Hash;
use Illuminate\Hashing\AbstractHasher;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use RuntimeException;
class Sha1Hasher extends AbstractHasher implements HasherContract {
public function __construct(array $options = []) {
}
public function make($value, array $options = []) {
$hash = sha1(env('SEC_SALT').$value);
if ($hash === false) {
throw new RuntimeException('Sha1 hashing not supported.');
}
return $hash;
}
public function check($value, $hashedValue, array $options = []) {
return ($this->make($value) == $hashedValue)?true:false;
}
public function needsRehash($hashedValue, array $options = array()): bool {
return false;
}
}
I would use the default HashServiceProvider and register a new driver into it. It would also make the switch back from sha1 to bcrypt even quicker once you completed the transitioning phase.
1) You have have to create a class which extends the Illuminate\Hashing\AbstractHasher or at least implements the Illuminate\Contracts\Hashing\Hasher. Take a look at the current Bcrypt driver implementation as a reference on GitHub.
The CustomHasher class you provided should work just fine as a driver, I would just rename it to avoid confusion with naming.
2) Now you can register the hash drivers in a service provider like:
public function boot()
{
$this->app->make('hash')->extend('sha1', function () {
// Just create the driver instance of the class you created in the step 1
return new YourCustomSha1Hasher();
});
}
3) Then in your config/hashing.php file, set the driver to 'sha1' (must be equal to the first parameter of the extend function call.
4) It should work straight out of the box, and to choose a different hashing driver, just change the config/hashing.php configuration file with the driver that you want to use for hashing.
Note: the whole code hasn't been tested, but I looked through the source code to come up with this solution that should work. Just comment anything isn't working as expected so I can fix my answer.
I need to make a change to the retrieveUser() function within Illuminate/Broadcasting/Broadcasters/Broadcaster.php.
The change works if I edit the class directly, but I have heard that you are not supposed to do that because it is difficult to track changes to the source code and because it will get overwritten when upgrading Laravel or when pushing to production.
So if I wanted to write my own modified retrieveUser() function for the Broadcaster class (it happens to be an abstract class which implements BroadcasterContract), then where and how would I do that?
Original function:
/**
* Retrieve the authenticated user using the configured guard (if any).
*
* #param \Illuminate\Http\Request $request
* #param string $channel
* #return mixed
*/
protected function retrieveUser($request, $channel)
{
$options = $this->retrieveChannelOptions($channel);
$guards = $options['guards'] ?? null;
if (is_null($guards)) {
return $request->user();
}
foreach (Arr::wrap($guards) as $guard) {
if ($user = $request->user($guard)) {
return $user;
}
}
}
New function:
protected function retrieveUser($request, $channel)
{
$options = $this->retrieveChannelOptions($channel);
$guards = $options['guards'] ?? null;
if (is_null($guards)) {
$token = $request->header('Token');
$id = Crypt::decrypt($token);
$user = User::find($id);
return $user;
}
foreach (Arr::wrap($guards) as $guard) {
if ($user = $request->user($guard)) {
return $user;
}
}
}
UPDATE
As #ggdx pointed out in the comments, I can override the class by doing class yourClass extends Illuminate\Broadcasting\Broadcasters\Broadcaster
However, I still don't know where to put this new class within the Laravel framework. I tried creating the new class in the /app route, but that did not work.
I'm not completely sure what you are trying to accomplish. But I think making a custom driver for a guard will do what you want. Looking at the docs https://laravel.com/docs/5.8/authentication#adding-custom-guards
You can do this in the boot method of your AuthServiceProvider.
Auth::viaRequest('custom-token', function ($request) {
return User::find(Crypt::decrypt($request->header('Token')));
});
Also, make sure to select it as the driver for your guard in your auth.php config file.
In my Lumen app, when I execute
php artisan migrate --seed
it works well.
But when I try to run my tests with phpunit, it doesn't run migration from a Laravel package that I coded, so all tests fail
I run my migrations in my test with :
Artisan::call('migrate');
I use in memory testing for faster running.
Here is my Lumen app Testcase.php
abstract class TestCase extends Laravel\Lumen\Testing\TestCase
{
/** #var array */
protected $dispatchedNotifications = [];
protected static $applicationRefreshed = false;
/**
* Creates the application.
*
* #return \Laravel\Lumen\Application
*/
/**
* Creates the application.
*
*/
public function createApplication()
{
return self::initialize();
}
private static $configurationApp = null;
public static function initialize()
{
$app = require __DIR__ . '/../bootstrap/app.php';
if (is_null(self::$configurationApp)) {
$app->environment('testing');
if (config('database.default') == 'sqlite') {
$db = app()->make('db');
$db->connection()->getPdo()->exec("pragma foreign_keys=1");
}
Artisan::call('migrate');
Artisan::call('db:seed');
self::$configurationApp = $app;
}
return $app;
}
/**
* Refresh the application instance.
*
* #return void
*/
protected function forceRefreshApplication()
{
if (!is_null($this->app)) {
$this->app->flush();
}
$this->app = null;
self::$configurationApp = null;
self::$applicationRefreshed = true;
parent::refreshApplication();
}
...
In my package, I use in the boot method of service provider:
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
and then a test example:
class TournamentsTest extends TestCase
{
use DatabaseTransactions, AttachJwtToken;
protected $initialTournamentNum = 6;
protected $defaultPagintation = 25;
protected $user;
/** #test */
public function user_can_see_tournament_list()
{
$response = $this
->call('GET', '/tournaments');
$this->assertEquals(HttpResponse::HTTP_OK, $response->status());
}
...
All my test fail with:
PDOException: SQLSTATE[HY000]: General error: 1 no such table: ken_venue
ken_venue is a table that come from the laravel package
In fact, I have this same package working well in a Laravel 5.7 application. but I am migrating this app to a Lumen app.
Any idea why is it happening ?
A few remarks first:
Your test function does not start with test, for me in Laravel such tests would not execute.
Second try this to run the seeds instead of calling migrate and seed by hand
namespace Tests\Unit;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;
class ShippingCostTest extends TestCase
{
use DatabaseMigrations; // call migrations this way
public function testDoesApply() // start your function with the word 'test'
$this->assertTrue(true); // call assert functions
}
protected function setUp() // use this function for setup
{
parent::setUp();
$this->seed(); // call this for seeding
}
}
Third: do you use the same databast type (e.g. MySQL in both cases), because of you use sqlite for testing the syntax might break all of the sudden because of differences between systems.
I have a Laravel 5.5 App where I have a Service Provider which I use to put some stuff in the request->attributes to access it everywhere (simplified):
namespace App\Providers;
use App\Models\Domain;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
class GlobalVarsServiceProvider extends ServiceProvider
{
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap the application services.
*
* #param Request $request
*
* #return void
*/
public function boot(Request $request)
{
$domain = .. get domain with language and some logic and cache because of multiple domains ..
$request->attributes->add(['domain' => $domain]);
}
}
I do this in a Service Provider, because then I can already use it in other Service Providers like my ViewComposerServiceProvider, where I compose some stuff for the Views. I'm able to access $domain everywhere like this:
$this->domain = $request->attributes->get('domain');
It works great. BUT not in testing. When I want to access $domain in a Unit Test in a middleware the $request->attributes are empty (In UnitTests as in DuskTests either).
It looks like the testing environment uses a different Request Lifecycle? If yes, what else is different in the testing environment?
What am I doing wrong?
-- Edit --
Test Example:
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
TestCase uses trait MakesHttpRequests which has method call. When you use get method in your tests, it's simply a shortcut to this.
In your test you can use it like this:
$this->call('GET', '/url/here', $yourRequestParametersHere);
I have a Behat FeatureContext for which I want to swap a Laravel implementation of a given class with a mocked one.
so I have this method, with a #beforeSuite annotation
/**
* #static
* #beforeSuite
*/
public static function mockData()
{
$unitTesting = true;
$testEnvironment = 'acceptance';
$app = require_once __DIR__.'/../../../bootstrap/start.php';
$app->boot();
$fakeDataRetriever = m::mock('My\Data\Api\Retriever');
$fakeData = [
'fake_name' => 'fake_value'
];
$fakeDataRetriever->shouldReceive('getData')->andReturn($fakeData);
$app->instance('My\Data\Api\Retriever', $fakeDataRetriever);
}
So I see the Laravel app and the fake data being swapped, but when I run Behat, it is being ignored, meaning Laravel is using the actual implementation instead of the fake one.
I'm using Laravel 4.2
Does someone know a way to swap Laravel implementations when running Behat?
The reason I need this is because the data is coming from remote API and I want the test to run without hitting the API.
I'm not too familiar with Behat besides what I just read in a quick tutorial to see if I can help found here... http://code.tutsplus.com/tutorials/laravel-bdd-and-you-lets-get-started--cms-22155
It looks like you are creating a new instance of Laravel, setting an instance implementation inside of it, then you are not doing anything with the Laravel instance. What's likely happening next is the testing environment is then going ahead and using its own instance of Laravel to run the tests on.
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
use PHPUnit_Framework_Assert as PHPUnit;
use Symfony\Component\DomCrawler\Crawler;
use Illuminate\Foundation\Testing\ApplicationTrait;
/**
* Behat context class.
*/
class LaravelFeatureContext implements SnippetAcceptingContext
{
/**
* Responsible for providing a Laravel app instance.
*/
use ApplicationTrait;
/**
* Initializes context.
*
* Every scenario gets its own context object.
* You can also pass arbitrary arguments to the context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* #BeforeScenario
*/
public function setUp()
{
if ( ! $this->app)
{
$this->refreshApplication();
}
}
/**
* Creates the application.
*
* #return \Symfony\Component\HttpKernel\HttpKernelInterface
*/
public function createApplication()
{
$unitTesting = true;
$testEnvironment = 'testing';
return require __DIR__.'/../../bootstrap/start.php';
}
/**
* #static
* #beforeSuite
*/
public function mockData()
{
$fakeDataRetriever = m::mock('My\Data\Api\Retriever');
$fakeData = [
'fake_name' => 'fake_value'
];
$fakeDataRetriever->shouldReceive('getData')->andReturn($fakeData);
$this->app->instance('My\Data\Api\Retriever', $fakeDataRetriever);
}
}