1

I'm trying to mock a third-party class called TwoFactorAuth, so in a test, I expect true to be returned to a method call. False is returned from the real class.

$this->mock(TwoFactorAuth::class, function (MockInterface $mock) {
   $mock
       ->shouldReceive('verifyCode')
       ->andReturn(true);
});

$tfa = new TwoFactorAuth();
$valid = $tfa->verifyCode('667CNVRAUMQNNQN6', '123456');

dd($valid);//returns false

Ideally, I shouldn't have to change how the TwoFactorAuth class is being called, I've extracted this for posting here, but in my code the TwoFactorAuth class is used in a controller and not in the test directly.

But even with the above the mocked version isn't being used I don't think.

This version works but I had to change the implementation

$mock = Mockery::mock(TwoFactorAuth::class);
$mock->shouldReceive('verifyCode')->once()->andReturn(true);

// Bind the mock instance to the Laravel service container
$this->app->instance(TwoFactorAuth::class, $mock);

// Resolve the mocked instance from the container
$resolvedMock = app(TwoFactorAuth::class);

// Use the mocked instance to call verifyCode
$valid = $resolvedMock->verifyCode('667CNVRAUMQNNQN6', '123456');
dd($valid);//return true

In summary, the goal is to call a controller and mock a result so my test would call

$this->post(route('admin.2fa'), ['code' => '123456'])
->assertSessionHasNoErrors()
->assertRedirect(route('dashboard'))

The method I'm trying to test:

public function update(Request $request): Redirector|RedirectResponse
{
    $request->validate([
        'code' => 'required|string|min:6',
    ]);

    $tfa = new TwoFactorAuth();
    $valid = $tfa->verifyCode(auth()->user()->two_fa_secret_key, $request->input('code'));

    if ($valid === false) {
        return back()->withErrors('Code is invalid please try again.');
    }

    session()->forget('2fa-login');

    return redirect(route('dashboard'));
}
Dave
  • 878
  • 8
  • 19
  • 2
    You did not uderstand how work dependency injection. When you use new keyword in application - you ruin all. Newer use new - and all will work fine. – Maksim Aug 13 '23 at 16:18
  • yes, DI really wins here. – Dave Aug 13 '23 at 20:26

1 Answers1

1

I changed the class to use dependency injected, once I changed the method to:

public function update(Request $request, TwoFactorAuth $twoFactorAuth)

Then I was able to mock the class and run tests:

test('can use 2fa code and redirects', function () {
    session(['2fa-login' => true]);

    $this->mock(TwoFactorAuth::class, function (MockInterface $mock) {
       $mock
           ->shouldReceive('verifyCode')
           ->andReturn(true);
    });

    auth()->user()->update(['two_fa_secret_key' => 'VMR466AB62ZBOKHE']);

    $this->post(route('admin.2fa'), ['code' => '123456'])
        ->assertRedirect(route('dashboard'));
});
Dave
  • 878
  • 8
  • 19
  • 1
    Either use DI like you did, or use `$twoFactorAuth = app(TwoFactorAuth::class);` or `$twoFactorAuth = resolve(TwoFactorAuth::class);` (the last one is an alias of `app`, I prefer it) – matiaslauriti Aug 13 '23 at 23:27