Testable Controllers with dependencies

前端 未结 3 889
无人及你
无人及你 2021-01-30 23:47

How can I resolve dependencies to a controller that is testable?

How it works: A URI is routed to a Controller, a Controller may have dependencies to pe

3条回答
  •  说谎
    说谎 (楼主)
    2021-01-31 00:20

    I have tried this from http://culttt.com/2013/07/15/how-to-structure-testable-controllers-in-laravel-4/

    How you should structure your Controllers to make them testable.?

    Testing your Controllers is a critical aspect of building a solid web application, but it is important that you only tests the appropriate bits of your application.

    Fortunately, Laravel 4 makes separating the concerns of your Controller really easy. This makes testing your Controllers really straight forward as long as you have structured them correctly.

    What should I be testing in my Controller?

    Before I get into how to structure your Controllers for testability, first its important to understand what exactly we need to test for.

    As I mentioned in Setting up your first Laravel 4 Controller, Controllers should only be concerned with moving data between the Model and the View. You don’t need to verify that the database is pulling the correct data, only that the Controller is calling the right method. Therefore your Controller tests should never touch the database.

    This is really what I’m going to be showing you today because by default it is pretty easy to slip into coupling the Controller and the Model together. An example of bad practice

    As a way of illustrating what I’m trying to avoid, here is an example of a Controller method:

    public function index()
    {
      return User::all();
    }
    

    This is a bad practice because we have no way of mocking User::all(); and so the associated test will be forced to hit the database.

    Dependency Injection to the rescue

    In order to get around this problem, we have to inject the dependency into the Controller. Dependency Injection is where you pass the class an instance of an object, rather than letting that object create the instance for its self.

    By injecting the dependency into the Controller, we can pass the class a mock instead of the database instead of the actual database object itself during our tests. This means we can test the functionality of the Controller without ever touching the database.

    As a general guide, anywhere you see a class that is creating an instance of another object it is usually a sign that this could be handled better with dependency injection. You never want your objects to be tightly coupled and so by not allowing a class to instantiate another class you can prevent this from happening.

    Automatic Resolution

    Laravel 4 has a beautiful way of handling Dependancy Injection. This means you can resolve classes without any configuration at all in many scenarios.

    This means that if you pass a class an instance of another class through the constructor, Laravel will automatically inject that dependency for you!

    Basically, everything will work without any configuration on your part.

    Injecting the database into a Controller

    So now you understand the problem and the theory of the solution, we can now fix the Controller so it isn’t coupled to the database.

    If you remember back to last week’s post on Laravel Repositories, you might have noticed that I already fixed this problem.

    So instead of doing:

    public function index()
    {
      return User::all();
    }
    

    I did:

    public function __construct(User $user)
    {
      $this->user = $user;
    }
    
    /**
     * Display a listing of the resource.
     *
     * @return Response
     */
    public function index()
    {
      return $this->user->all();
    }
    

    When the UserController class is created, the __construct method is automatically run. The __construct method is injected with an instance of the User repository, which is then set on the $this->user property of the class.

    Now whenever you want to use the database in your methods, you can use the $this->user instance.

    Mocking the database in your Controller tests

    The real magic happens when you come to write your Controller tests. Now that you are passing an instance of the database to the Controller, you can mock the database instead of actually hitting the database. This will not only improve performance, but you won’t have any test data lying around after your tests.

    First thing I’m going to do is to create a new folder under the tests directory called functional. I like to think of Controller tests as being functional tests because we are testing the incoming traffic and the rendered view.

    Next I’m going to create a file called UserControllerTest.php and write the following boilerplate code:

    Mocking with Mockery

    If you remember back to my post, What is Test Driven Development?, I talked about Mocks as being, a replacement for dependent objects.

    In order to create Mocks for the tests in Cribbb, I’m going to use a fantastic package called Mockery.

    Mockery allows you to mock objects in your project so you don’t have to use the real dependency. By mocking an object, you can tell Mockery which method you would like to call and what you would like to be returned.

    This enables you to isolate your dependencies so you only make the required Controller calls in order for the test to pass.

    For example, if you wanted to call the all() method on your database object, instead of actually hitting the database you can mock the call by telling Mockery you want to call the all() method and it should return an expected value. You aren’t testing whether the database can return records or not, you only care about being able to trigger the method and deal with the return value.

    Installing Mockery Like all good PHP packages, Mockery can be installed through Composer.

    To install Mockery through Composer, add the following line to your composer.json file:

    "require-dev": {
      "mockery/mockery": "dev-master"
    }
    

    Next, install the package:

    composer install --dev
    

    Setting up Mockery

    Now to set up Mockery, we have to create a couple of set up methods in the test file:

    public function setUp()
    {
      parent::setUp();
    
      $this->mock = $this->mock('Cribbb\Storage\User\UserRepository');
    }
    
    public function mock($class)
    {
      $mock = Mockery::mock($class);
    
      $this->app->instance($class, $mock);
    
      return $mock;
    }
    

    The setUp() method is run before any of the tests. Here we are grabbing a copy of the UserRepository and creating a new mock.

    In the mock() method, $this->app->instance tells Laravel’s IoC container to bind the $mock instance to the UserRepository class. This means that whenever Laravel wants to use this class, it will use the mock instead. Writing your first Controller test

    Next you can write your first Controller test:

    public function testIndex()
    {
      $this->mock->shouldReceive('all')->once();
    
      $this->call('GET', 'user');
    
      $this->assertResponseOk();
    }
    

    In this test I’m asking the mock to call the all() method once on the UserRepository. I then call the page using a GET request and then I assert that the response was ok.

    Conclusion

    Testing Controllers shouldn’t be as difficult or as complicated as it is made out to be. As long as you isolate the dependencies and only test the right bits, testing Controllers should be really straight forward.

    may this help you.

提交回复
热议问题