1
votes

I'm currently having some troubles in testing a function in Laravel. This function is a simple save user function.

The current structure involves a User

class User extends Authenticatable

Then I have a UserController

class UserController extends Controller
{
protected $user;

public function __construct(User $user)
{
    $this->user = $user;
    $this->middleware('admins');
}

The save function is defined on the UserController class, this class only assigns the request variables and uses Eloquent save function to save to database.

The function signature is the following:

public function storeUser($request)
{

    $this->user->name       = $request->name;
    $this->user->email      = $request->email;
    $this->user->country_id = $request->country_id;

    return $this->user->save();
}

The NewAccountRequest object extends from Request and has the validation rules for the request.

class NewAccountRequest extends Request
{
public function authorize()
{
    return true;
}

public function rules()
{
    return [
        'name' => 'required|max:255',
        'email' => 'required|email|max:255|unique:user',
        'password' => 'required|min:6|max:60',
    ];
}

}

My problem is how can I unit test this storeUser function.

I have the current test:

public function testSaveUserWithEmptyRequest()
{

    $user = $this->createMock(User::class);
    $controller = new UserController($user);

    $request = $this->createMock(NewAccountRequest::class);
    $store = $controller->storeUser($request);

    $this->assertFalse($store);
}

I'm mocking both User and NewAccountRequest, the problem is that the assertion should be false, from the Eloquent save. Instead I'm getting Null. Any idea on how can I correctly test the function?

1
What's inside saveUser function, are you missing a return statement ?Shady Atef
@ShadyAtef Updated the question with the StoreUser code and a correction to the call name.Cláudio Ribeiro
I have got it, you mock the User::class.. In PHPUnit createMock replaces all the functions of an object with dummy ones that doe nothing and return nulls. In fact to test database operation in laravel, you have to use one of the two traits provided DatabaseMigrations or DatabaseTransactionsShady Atef
But I'm not mocking the UserController, shouldn't it return false because the request is empty?Cláudio Ribeiro
But the return line simply calls a function on the $user that you have mocked and passedShady Atef

1 Answers

0
votes
<?php
namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseTransactions; // Laravel will automatically roll back changes that happens in every test 


    public function testSaveUserWithEmptyRequest()
    {

        $user = new User(); 
        $controller = new UserController($user);

        $request = $this->createMock(NewAccountRequest::class);
        $store = $controller->storeUser($request);

        $this->assertFalse($store);
    }

}

This is exactly what you are trying to do, but unfortunately this will fail due to database exceptions... Mocking a request or even manually crafting it will not do the data input validation.. and in your example password field is not nullable and will cause PDOException: SQLSTATE[HY000]: General error: 1364 Field 'password' doesn't have a default value


The recommended way to test functions depending on request, is to use http test helpers provided by laravel like $response = $this->post('/user', ['name' => 'Sally']);


A much better approach is to use the repository design pattern.. this simply means collate your database functions into separate classes and call it from controllers ..