method does not exist on this mock object - Larave

2019-04-10 11:29发布

i'm trying to test a simple class. I'm following this tutorial( http://code.tutsplus.com/tutorials/testing-laravel-controllers--net-31456 ).

I have this error, while running tests:

Method Mockery_0_App_Interfaces_MealTypeRepositoryInterface::getValidator() does not exist on this mock object

Im using repository structure. So, my controller calls repository and that returns Eloquent's response.

I'm relatively new in php and laravel. And I've started learning to test a few days ago, so I'm sorry for that messy code.

My test case:

class MealTypeControllerTest extends TestCase
{
public function setUp()
{
    parent::setUp();

    $this->mock = Mockery::mock('App\Interfaces\MealTypeRepositoryInterface');
    $this->app->instance('App\Interfaces\MealTypeRepositoryInterface' , $this->mock);
}
public function tearDown()
{
    Mockery::close();
}

public function testIndex()
{
    $this->mock
         ->shouldReceive('all')
         ->once()
         ->andReturn(['mealTypes' => (object)['id' => 1 , 'name' => 'jidlo']]);

    $this->call('GET' , 'mealType');

    $this->assertViewHas('mealTypes');
}

public function testStoreFails()
{
    $input = ['name' => 'x'];

    $this->mock
         ->shouldReceive('getValidator')
         ->once()
         ->andReturn(Mockery::mock(['fails' => true]));

    $this->mock
         ->shouldReceive('create')
         ->once()
         ->with($input);


    $this->call('POST' , 'mealType' , $input ); // this line throws the error

    $this->assertRedirectedToRoute('mealType.create');//->withErrors();

    $this->assertSessionHasErrors('name');
}

}

My EloquentMealTypeRepository: Nothing really interesting.

class EloquentMealTypeRepository implements MealTypeRepositoryInterface
{
public function all()
{
    return MealType::all();
}

public function find($id)
{
    return MealType::find($id);
}

public function create($input)
{
    return MealType::create($input);
}

public function getValidator($input)
{
    return MealType::getValidator($input);
}
}

My eloquent implementation: Nothing really interresting,too.

class MealType extends Model
{
private $validator;

/**
 * The database table used by the model.
 *
 * @var string
 */
protected $table = 'meal_types';

/**
 * The attributes that are mass assignable.
 *
 * @var array
 */
protected $fillable = ['name'];

/**
 * The attributes excluded from the model's JSON form.
 *
 * @var array
 */
protected $hidden = [];

public function meals()
{
    return $this->hasMany('Meal');
}

public static function getValidator($fields)
{
    return Validator::make($fields, ['name' => 'required|min:3'] );
}
}

My MealTypeRepositoryInterface:

interface MealTypeRepositoryInterface
{
public function all();

public function find($id);

public function create($input);

public function getValidator($input);
}

And finally, My controller:

class MealTypeController extends Controller {
protected $mealType;

public function __construct(MealType $mealType)
{   
    $this->mealType = $mealType;
}


/**
 * Display a listing of the resource.
 *
 * @return Response
 */
public function index()
{
    $mealTypes = $this->mealType->all();
    return View::make('mealTypes.index')->with('mealTypes' ,$mealTypes);
}

/**
 * Show the form for creating a new resource.
 *
 * @return Response
 */
public function create()
{
    $mealType = new MealTypeEloquent;
    $action = 'MealTypeController@store';
    $method = 'POST';

    return View::make('mealTypes.create_edit', compact('mealType' , 'action' , 'method') );     
}

/**
 * Validator does not work properly in tests.
 * Store a newly created resource in storage.
 *
 * @return Response
 */
public function store(Request $request)
{
    $input = ['name' => $request->input('name')];

    $mealType = new $this->mealType;

    $v = $mealType->getValidator($input);

    if( $v->passes() )
    {
        $this->mealType->create($input);
        return Redirect::to('mealType');
    }
    else
    {
        $this->errors = $v;
        return Redirect::to('mealType/create')->withErrors($v);
    }
}

/**
 * Display the specified resource.
 *
 * @param  int  $id
 * @return Response
 */
public function show($id)
{
    return View::make('mealTypes.show' , ['mealType' => $this->mealType->find($id)]);
}

/**
 * Show the form for editing the specified resource.
 *
 * @param  int  $id
 * @return Response
 */
public function edit($id)
{
    $mealType = $this->mealType->find($id);
    $action = 'MealTypeController@update';
    $method = 'PATCH';
    return View::make('mealTypes.create_edit')->with(compact('mealType' , 'action' , 'method'));
}

/**
 * Update the specified resource in storage.
 *
 * @param  int  $id
 * @return Response
 */
public function update($id)
{
    $mealType = $this->mealType->find($id);
    $mealType->name = \Input::get('name');
    $mealType->save();
    return redirect('mealType');
}

/**
 * Remove the specified resource from storage.
 *
 * @param  int  $id
 * @return Response
 */
public function destroy($id)
{
    $this->mealType->find($id)->delete();
    return redirect('mealType');
}

}

That should be everything. It's worth to say that the application works, just tests are screwed up. Does anybody know, why is that happening? I cant see a difference between methods of TestCase - testIndex and testStoreFails, why method "all" is found and "getValidator" is not. I will be thankful for any tips of advices.

2条回答
成全新的幸福
2楼-- · 2019-04-10 12:06

Perhaps an aside, but directly relevant to anyone finding this question by its title:

If:

  1. You are getting the error BadMethodCallException: Method Mockery_0_MyClass::myMethod() does not exist on this mock object, and
  2. none of your mocks are picking up any of your subject's methods, and
  3. your classes are being autoloaded, (e.g. using composer)

then before making your mock object, you need to force the loading of that subject, by using this line of code:

spl_autoload_call('MyNamespace\MyClass'); 

Then you can mock it:

$mock = \Mockery::mock('MyNamespace\MyClass');

In my PHPUnit tests, I often put that first line into the setUpBeforeClass() static function, so it only gets called once and is isolated from tests being added/deleted. So the Test class looks like this:

class MyClassTest extends PHPUnit_Framework_TestCase {
    public static function setUpBeforeClass() {
        parent::setUpBeforeClass();
        spl_autoload_call('Jodes\MyClass'); 
    }
    public function testIt(){
        $mock = \Mockery::mock('Jodes\MyClass');
    }
}

I have forgotten about this three times now, each time spending an hour or two wondering what on earth the problem was!

查看更多
Explosion°爆炸
3楼-- · 2019-04-10 12:10

I have found a source of this bug in controller. calling wrong

$v = $mealType->getValidator($input);

instead of right

$v = $this->mealType->getValidator($input);

查看更多
登录 后发表回答