2015-08-05 9 views
10

Zunächst einmal bin ich bewusst die docs Zustände:Mocking Antrag in Laravel 5.1 (Ist) Unit Test

Note: You should not mock the Request facade. Instead, pass the input you desire into the HTTP helper methods such as call and post when running your test.

Aber diese Art von Tests sind eher wie Integration oder funktionelle da selbst Obwohl Sie einen Controller (SUT) testen, entkoppeln Sie ihn nicht von seinen Abhängigkeiten (Request und andere, mehr dazu später).

Also, was ich tue, um die richtige TDD Schleife zu tun, verhöhnt die Repository, Response und Request (was ich habe Probleme mit).

Mein Test sieht wie folgt aus:

public function test__it_shows_a_list_of_categories() { 
    $categories = []; 
    $this->repositoryMock->shouldReceive('getAll') 
     ->withNoArgs() 
     ->once() 
     ->andReturn($categories); 
    Response::shouldReceive('view') 
     ->once() 
     ->with('categories.admin.index') 
     ->andReturnSelf(); 
    Response::shouldReceive('with') 
     ->once() 
     ->with('categories', $categories) 
     ->andReturnSelf(); 

    $this->sut->index(); 

    // Assertions as mock expectations 
} 

Das funktioniert völlig in Ordnung, und sie folgen der anordnen, Act, Assert Stil.

Das Problem ist mit Request, wie folgt aus:

public function test__it_stores_a_category() { 
    Redirect::shouldReceive('route') 
     ->once() 
     ->with('categories.admin.index') 
     ->andReturnSelf(); 

    Request::shouldReceive('only') 
     ->once() 
     ->with('name') 
     ->andReturn(['name' => 'foo']); 

    $this->repositoryMock->shouldReceive('create') 
     ->once() 
     ->with(['name' => 'foo']); 

    // Laravel facades wont expose Mockery#getMock() so this is a hackz 
    // in order to pass mocked dependency to the controller's method 
    $this->sut->store(Request::getFacadeRoot()); 

    // Assertions as mock expectations 
} 

Wie man sehen kann ich Request::only('name') Anruf verspottet haben. Aber wenn ich $ phpunit laufen bekomme ich folgende Fehlermeldung:

BadMethodCallException: Method Mockery_3_Illuminate_Http_Request::setUserResolver() does not exist on this mock object 

Da ich nicht direkt setUserResolver() von meinem Controller nennen, das bedeutet, es direkt durch die Umsetzung der Request genannt wird. Aber warum? Ich verspottete den Methodenaufruf, er sollte keine Abhängigkeit aufrufen.

Was mache ich hier falsch, warum bekomme ich diese Fehlermeldung?

PS: Als ein Bonus, schaue ich es falsch, indem ich TDD mit Unit Tests auf Laravel-Framework gezwungen, wie es scheint, die Dokumentation ist auf Integrationstests durch Kopplung Interaktion zwischen Abhängigkeiten und SUT mit $this->call() ausgerichtet?

+0

in diese heute Ran. Related: https://twitter.com/laravelphp/status/556568018864459776 – Ravan

Antwort

3

Sie werden immer auf Probleme stoßen, wenn Sie versuchen, die Testcontroller ordnungsgemäß zu testen. Ich empfehle, sie mit etwas wie Codeception zu testen. Mithilfe von Abnahmetests können Sie sicherstellen, dass Ihre Controller/Sichten alle Daten entsprechend verarbeiten.

+3

Ja, Controller sind wie der "Klebstoff" Ihrer Anwendung. Sie kombinieren viele Ihrer Dienste zu Aktionen. Der Point-of-Unit-Test besteht darin, Code-Einheiten isoliert zu testen. Controller sollen keine kleine Einheit sein, sie binden viele Dinge zusammen. Es ist ein viel besserer Anwendungsfall für einen Akzeptanz- oder Funktionstest. –

+1

Das Problem besteht nicht nur bei Controllern. Heute habe ich einen Funktionstest für ein individuelles Repository-Merkmal versucht, bei dem die Ergebnisse durch Laravels Paginator paginiert werden. Es stellte sich heraus, dass die paginate() - Methode von Laravel die Seitennummer direkt aus Request-> input() liest, also muss ich irgendwie nachspionieren, um die korrekte Seitennummer zurückzugeben, wie von meinem Test gefordert. – JustAMartin

+0

@JustAMartin Ich lief heute genau in der gleichen Situation, Meine Repository 'GetPageOfDealers (Request $ Anfrage) 'erhalten ein Request-Objekt dann filtern und paginieren das Ergebnis entsprechend. Aber um das zu testen, muss ich Request vortäuschen. – UniFreak

7

Einheit, die einen Controller testen, wenn Sie Laravel verwenden, scheint keine gute Idee zu sein. Ich würde mich nicht mit den einzelnen Methoden befassen, die auf Request, Response und vielleicht sogar den Repository-Klassen aufgerufen werden, wenn man den Kontext eines Controllers betrachtet.

Darüber hinaus macht das Testen eines Controllers, der zu einem Framework gehört, keinen Sinn, da Sie den Test sut von seinen Abhängigkeiten entkoppeln wollen, da Sie diesen Controller nur in diesem Framework mit den angegebenen Abhängigkeiten verwenden können.

Da die Anfrage, die Antwort und andere Klassen vollständig getestet wurden (entweder über die zugrunde liegende Symfony-Klasse oder über Laravel selbst), würde ich mich als Entwickler nur mit dem Testen des Codes befassen, den ich besitze.

Ich würde einen Abnahmetest schreiben.

<?php 

use App\User; 
use App\Page; 
use App\Template; 
use App\PageType; 
use Illuminate\Foundation\Testing\WithoutMiddleware; 
use Illuminate\Foundation\Testing\DatabaseMigrations; 
use Illuminate\Foundation\Testing\DatabaseTransactions; 

class CategoryControllerTest extends TestCase 
{ 
    use DatabaseTransactions; 

    /** @test */ 
    public function test__it_shows_a_paginated_list_of_categories() 
    { 
     // Arrange 
     $categories = factory(Category::class, 30)->create(); 

     // Act 
     $this->visit('/categories') 

     // Assert 
      ->see('Total categories: 30') 
      // Additional assertions to verify the right categories can be seen may be a useful additional test 
      ->seePageIs('/categories') 
      ->click('Next') 
      ->seePageIs('/categories?page=2') 
      ->click('Previous') 
      ->seePageIs('/categories?page=1'); 
    } 

} 

Da dieser Test die DatabaseTransactions Eigenschaft verwendet, ist es einfach, die arrangieren Teil des Prozesses durchzuführen, was fast können Sie dies als eine pseudo-Unit-Test lesen (aber das ist eine leichte Strecke der Phantasie).

Am wichtigsten, dieser Test bestätigt, dass meine Erwartungen erfüllt werden. Mein Test heißt test_it_shows_a_paginated_list_of_categories und meine Version des Tests macht genau das. Ich glaube, dass die Unit-Test-Route nur bestätigt, dass eine Reihe von Methoden aufgerufen wird, überprüft aber nicht, dass ich eine Liste der angegebenen Kategorien auf einer Seite zeige.

+0

Mein persönlicher Anwendungsfall ist, dass ich eine Hilfsfunktion erstellt habe, die die Funktion request() -> root() verwendet, um ein Ergebnis zu bestimmen. z.B. Wenn die URL das ist, mach das. Also, ich möchte meine Hilfsfunktion testen, indem ich request() -> root() spreche, um bestimmte Werte zurückzugeben ... aber ich kann nicht. Gibt es keine andere Möglichkeit, die request() -Fassade korrekt nachzuahmen? – Maccath

+1

Ich denke, wenn Sie die Fassade verspotten müssen, gibt es eine Gelegenheit zum Refactoring. Für Ihre Hilfsfunktion würde ich erwarten, dass sie einen String-Parameter '' '$ url''' akzeptiert. Auf diese Weise können Sie Ihre Hilfsfunktion problemlos mit jedem Parameter testen, den Sie übergeben möchten. – Amo

0

Ich habe versucht, die Anfrage für meine Tests zu verspotten, ohne Erfolg. Hier ist, wie ich testen, ob Element gespeichert wird:

public function test__it_stores_a_category() { 
    $this->action(
      'POST', 
      '[email protected]', 
      [], 
      [ 
       'name' => 'foo', 
      ] 
     ); 

    $this->assertRedirectedTo('categories/admin/index'); 

    $this->seeInDatabase('categories', ['name' => 'foo']); 
} 

Hoffe, dass es eine Hilfe ist