1

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?

6
  • What's inside saveUser function, are you missing a return statement ? Commented Feb 21, 2017 at 10:35
  • @ShadyAtef Updated the question with the StoreUser code and a correction to the call name. Commented Feb 21, 2017 at 10:39
  • 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 DatabaseTransactions Commented Feb 21, 2017 at 10:47
  • But I'm not mocking the UserController, shouldn't it return false because the request is empty? Commented Feb 21, 2017 at 10:50
  • But the return line simply calls a function on the $user that you have mocked and passed Commented Feb 21, 2017 at 10:52

1 Answer 1

0
<?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 ..

Sign up to request clarification or add additional context in comments.

2 Comments

Will try testing with the routes, will give feedback when I do. thanks!
Oh something else I forgot to say, you try $this->expectException(\PDOException::class); in the begining of the test to catch the thrown exception..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.