Laravel UnitTest not working when pass constructor has Interface

129 Views Asked by At

I'm new to UnitTest and trying to integrate it into my Laravel application, but I'm getting the below error:

Call to a member function findOne() on null
 at app/Services/User/UserService.php:32
    28▕         $this->userWebsiteRepository = $userWebsiteRepository;
    29▕     }
    30▕
    31▕     public function findOne($data = []){
 ➜  32▕         return $this->userRepository->findOne($data);
    33▕     }
    34▕

This is my code. AuthController.php

class AuthController extends Controller {

    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function show($id){
        return $this->userService->findOne(['id' => $id]);
    }
}

UserService.php

class UserService
{
    public $userRepository;

    public function __construct(UserRepositoryInterface $userRepository)
    {
        $this->userRepository = $userRepository;
    }
}

UserRepositoryInterface.php

interface UserRepositoryInterface
{
    public function findOne($data);
}

UserRepository.php

use App\Models\User;

class UserRepository implements UserRepositoryInterface
{

    private $model;

    public function __construct(User $user)
    {
        $this->model = $user;
    }

    public function findOne($data)
    {
        if (empty($data)) return false;
        $query = $this->model->with(['userWebsites', 'userWebsites.website', 'role']);
        if(!empty($data['id'])) $query = $query->where('id', $data['id']);
        return $query->first();
    }
}

RepositoryServiceProvider.php

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }
}

AuthControllerTest.php

class AuthControllerTest extends TestCase
{

    public $authController;
    public $userRepositoryInterfaceMockery;
    public $userServiceMokery;


    public function setUp(): void{
        $this->afterApplicationCreated(function (){
            $this->userRepositoryInterfaceMockery = Mockery::mock(UserRepositoryInterface::class)->makePartial();
            $this->userServiceMokery = Mockery::mock((new UserService(
                $this->app->instance(UserRepositoryInterface::class, $this->userRepositoryInterfaceMockery)
            ))::class)->makePartial();
            $this->authController = new AuthController(
                $this->app->instance(UserService::class, $this->userServiceMokery)
            );
        }
    }

    public function test_abc_function(){
        $res = $this->authController->abc(1);
    }

}

I was still able to instantiate the AuthController and it ran to the UserService. but it can't get the UserRepositoryInterface argument. I think the problem is that I passed the Interface in the constructor of the UserService. .What happened, please help me, thanks

1

There are 1 best solutions below

1
Bulent On

I don't know where $userService comes from to your controller's constructor, but it seems like it comes from nowhere. You need to pass it as argument, so Laravel can resolve its instance in service container.

class AuthController extends Controller {

    private $userService;

    public function __construct(
        private AuthService $authService,
        UserRepositoryInterface $userRepository
    ) {
        $this->userService = new UserService($userRepository);
    }

    public function show($id)
    {
        return $this->userService->findOne(['id' => $id]);
    }
}

Also there is literally no findOne method in UserService. You need one there.

class UserService
{
    public function __construct(private UserRepositoryInterface $userRepository)
    {
    }

    public function findOne(array $data)
    {
        return $this->userRepository->findOne($data);
    }
}

Update

In that case you need this in service provider:

$this->app->bind(UserRepositoryInterface::class, UserRepository::class);

$this->app->bind(UserService::class, function ($app) {
    return new UserService($app->make(UserRepositoryInterface::class));
});