Laravel 单元测试 - 控制器

Posted

技术标签:

【中文标题】Laravel 单元测试 - 控制器【英文标题】:Laravel Unit Testing - Controllers 【发布时间】:2020-08-12 07:29:35 【问题描述】:

我对 Laravel 很陌生,并且一直在阅读有关测试的文档,但是我不确定如何对我在下面发布的控制器进行单元测试。任何关于我将如何去做的建议都非常感谢。 下面的控制器是我为预订表单创建的 CRUD 控制器。

class BookingFormsController extends Controller

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    
        $bookingforms = BookingForm::orderBy('surgeryDate', 'asc')->paginate(5);
        return view('bookingforms.index')->with('bookingforms', $bookingforms);
    

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    
        return view('bookingforms.create');
    

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $booking)
    
      $this->validate($booking, [
      'requestID' => 'required',
      'patientID' => 'required',
      'patientForename' => 'required',
      'patientSurname'=> 'required',
      'patientSex' => 'required',
      'patientDOB' => 'required',
      'surgeryType' => 'required',
      'surgeryDate' => 'required',
      'performingSurgeon' => 'required',
      'TheatreRoomID' => 'required',
      'patientUrgency' => 'required',
      'patientNotes' => 'required',
      'bloodGroup' => 'required'
      ]);

      // Create new Booking Form
      $bookingform = new Bookingform;
      $bookingform->requestID = $booking->input('requestID');
      $bookingform->bookingID = $booking->input('bookingID');
      $bookingform->patientID = $booking->input('patientID');
      $bookingform->patientForename = $booking->input('patientForename');
      $bookingform->patientSurname = $booking->input('patientSurname');
      $bookingform->patientSex = $booking->input('patientSex');
      $bookingform->patientDOB = $booking->input('patientDOB');
      $bookingform->surgeryType = $booking->input('surgeryType');
      $bookingform->surgeryDate = $booking->input('surgeryDate');
      $bookingform->performingSurgeon = $booking->input('performingSurgeon');
      $bookingform->TheatreRoomID = $booking->input('TheatreRoomID');
      $bookingform->patientUrgency = $booking->input('patientUrgency');
      $bookingform->patientNotes = $booking->input('patientNotes');
      $bookingform->bloodGroup = $booking->input('bloodGroup');
      $bookingform->user_id = auth()->user()->id;

      //Save Booking form

      $bookingform->save();

      //redirect
      return redirect('/bookingforms')->with('success', 'Booking Submitted');
    

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($bookingID)
    
        $bookingform = BookingForm::find($bookingID);
        return view('bookingforms.show')->with('bookingform', $bookingform);
    

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($bookingID)
    
        $bookingform = BookingForm::find($bookingID);
        //check for correct user_id
        if(auth()->user()->id !==$bookingform->user_id)
            return redirect('/bookingforms')->with('danger', 'This is not your booking, please contact the Booker.');
        
        return view('bookingforms.edit')->with('bookingform', $bookingform);
    

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $booking, $bookingID)
    
      $this->validate($booking, [
      'patientID' => 'required',
      'patientForename' => 'required',
      'patientSurname'=> 'required',
      'patientSex' => 'required',
      'patientDOB' => 'required',
      'surgeryType' => 'required',
      'surgeryDate' => 'required',
      'performingSurgeon' => 'required',
      'TheatreRoomID' => 'required',
      'patientUrgency' => 'required',
      'patientNotes' => 'required',
      'bloodGroup' => 'required'
      ]);

      // Create new Booking Form
      $bookingform = Bookingform::find($bookingID);
      $bookingform->bookingID = $booking->input('bookingID');
      $bookingform->patientID = $booking->input('patientID');
      $bookingform->patientForename = $booking->input('patientForename');
      $bookingform->patientSurname = $booking->input('patientSurname');
      $bookingform->patientSex = $booking->input('patientSex');
      $bookingform->patientDOB = $booking->input('patientDOB');
      $bookingform->surgeryType = $booking->input('surgeryType');
      $bookingform->surgeryDate = $booking->input('surgeryDate');
      $bookingform->performingSurgeon = $booking->input('performingSurgeon');
      $bookingform->TheatreRoomID = $booking->input('TheatreRoomID');
      $bookingform->patientUrgency = $booking->input('patientUrgency');
      $bookingform->patientNotes = $booking->input('patientNotes');
      $bookingform->bloodGroup = $booking->input('bloodGroup');
      $bookingform->user_id = auth()->user()->id;

      //Save Booking form

      $bookingform->save();

      //redirect
      return redirect('/bookingforms')->with('success', 'Booking Updated');

    

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($bookingID)
    
        $bookingform = Bookingform::find($bookingID);
        if(auth()->user()->id !==$bookingform->user_id)
            return redirect('/bookingforms')->with('danger', 'This is not your booking, please contact the Booker.');
        
        $bookingform->delete();
        return redirect('/bookingforms')->with('success', 'Booking Removed');
    

【问题讨论】:

通常你会创建一个HTTP test,它会到达你需要进行单元测试的端点。 【参考方案1】:

您编写的控制器有点难以测试,因为它与Eloquent 模型紧密耦合。您最好通过添加存储库层并将其注入控制器来解耦它。

顺便说一句:您可以使用fillable attributes 来避免编写大量代码来填充BookingForm 的属性

现在,例如,您可以执行以下操作:

创建一个 BookingFormRepository 接口:

interface BookingFormRepository

    public function all();
    public function create(array $attributes);

    // etc ....


创建BookingFormRepository 的实现:

class BookingFormRepositoryImpl implements BookingRepository 

    public function all()
    
         return BookingForm::all();
    

    public function create(array $attributes)
    
        // Use fillable attributes for better readability
        $record = BookingForm::create($attributes);
        return $record;
    

    // Implement other methods ....

AppServiceProvider 中的register 方法中绑定你的实现:

App::bind(BookingFormRepository::class, BookingFormRepositoryImpl::class); 

然后在你的控制器中,注入BookingRepository 接口:

class BookingFormController extends Controller 

    private $bookingFormRepository;

    public function __construct(BookingFormRepository $bookingRepo)
    
        $this->bookingFormRepository = $bookingRepo;
    

    public function index()
    
        $bookings = $this->bookingFormRepository->all();
        return view('bookingform.index', $bookings);
    

    // .. other methods ... like `store`

现在控制器将很容易测试,只需模拟 BookingRepository 并对其进行调用断言:

class BookingFormControllerTest extends TestCase

     public function testIndexBookingForm()
     
          // Arrange

          $repository = Mockery::mock('BookingRepository');
          $repository->shouldReceive('all')->once()
              ->andReturn(['foobar']);
          App::instance('BookingRepository', $repository);


          // Act, Replace with your right url ...
          $this->get('bookings');

          // Assert
          $this->assertResponseOk();
          $this->assertViewHas('bookingforms.index', ['foobar']);
     

我推荐阅读 Taylor Otwell 的书“Laravel 从学徒到工匠”。

【讨论】:

【参考方案2】:

一个简单的例子:

class ExampleTest extends TestCase

    public function testBookingFormsIndex()
    
        $response = $this->get('index');
        $response->assertStatus('200');
    

    public function testBookingFormsCreate()
    
        $response = $this->get('create');
        $response->assertStatus('200');
    


就像我说的,上面是一个基本示例,它基于 laravel 的 HTTP 测试文档中的示例。

更多信息可以在这里找到: https://laravel.com/docs/7.x/http-tests

我还建议使用 laravel 请求来验证您的表单输入,这可以保持您的控制器干净并将代码放置在所属的位置。 可以在此处找到有关此主题的更多信息:https://laravel.com/docs/7.x/validation#creating-form-requests

【讨论】:

谢谢!我似乎收到的是 302 响应而不是 200,这是因为这些路由需要用户身份验证吗? 不,如果用户未通过身份验证,您将收到 403。看看这个网站,它解释了 302。developer.mozilla.org/en-US/docs/Web/HTTP/Status/302

以上是关于Laravel 单元测试 - 控制器的主要内容,如果未能解决你的问题,请参考以下文章

使用 PHPUnit 在 Laravel 控制器中进行单元测试 Guzzle

在 Laravel 中对控制器进行单元测试而不测试路由的最佳方法是啥

在 laravel 5.2 单元测试中模拟作业

Laravel 单元测试断言函数总是失败

Laravel 单元测试显示完全错误

Laravel - 输入未通过单元测试