Libraries should be able to provide mock instances #513

Closed
hans-d opened this Issue Jun 1, 2012 · 6 comments

Projects

None yet

5 participants

@hans-d
Contributor
hans-d commented Jun 1, 2012

For a controller, that uses a model for any of the crud, I want to able to unit test it without requiring the underlying database. Partly because I want to control the data sets and preparing the database is time consuming.

The current way to do so is to make use of the $_classes functionality, but that results in code like

$Customers = $this->_classes['customers'];
$customers = $Customers::goldlevel();

which is less natural as

$customers = Customers::goldlevel();

When in a domain service, where multiple models are involved, it happens all over the place.

And this is only done to allow for testing, not for enabling swapping part of the implementation like adapters etc.

For testing purposes, it would be great if in the test I could specify that when referring to app\models\Customers another class needs to be loaded, and after the test that could be made undone.

@mehlah
Contributor
mehlah commented Jun 1, 2012

Are you suggesting something different from :

  • Creating a MockCustomers class which extends your Customers model, and where you set a protected $_meta = array('connection' => false);, and mock, for example find() and save() methods.
  • in your test case class, use app\tests\mocks\MockCustomers as Customer;, and call your model methods with Customers::goldlevel();

That way, you'll create a valid default Customer object for tests
You'll avoid any database hit, your fixture data are right in front of you in your find() method. Your tests don't rely on any external datasource, and are less brittle and easier to read

@hans-d
Contributor
hans-d commented Jun 1, 2012

Yep... Using mockery it would allow me to keep the fixtures in my code, and better test behaviour

// partly code

$request = new Request();
$request->data['level'] = 'gold';

$model = m::mock('alias:mock\models\Customer')
$model->shouldReceive('goldlevel')
                ->once()
                ->andReturn(array(1,2,3,4));

// for 'lithium\net\http\Media' => 'lithium\tests\mocks\action\MockMediaClass',
// for 'app\models\Customer => 'mock\models\Customer'

$controller = CustomerController(compact('request'))
$controller->index();
$controller->render();

$expected = array(1,2,3,4);
$result = $controller->response->data['customers'];

$this->assertEqual($expected, $result);
@hans-d
Contributor
hans-d commented Jun 1, 2012

Trying out your point

in your test case class, use app\tests\mocks\MockCustomers as Customer;, and call your model methods with Customers::goldlevel();

I assume you made a type with as Customers (missing trailing S).

When I try this, it still uses the old/original Customers class. My test class is very bare

namespace app\tests\mocks\controllers

use mock\Customers as Customers;

class CustomerControllerMock extends \app\controllers\CustomerController { }
@nateabele
Member

I think what might be better here is to implement something in li3_fixtures to enable mocking of database connections for models, and asserting against the queries/providing results there. What do you think?

@jails
Contributor
jails commented Apr 13, 2013

Hey @BlaineSch, can you confirm this can be closed ?

@blainesch
Member

From what I can tell these are not unit tests, but integration tests. He's calling the controller, which calls the models.

I would assume the best approach for that would be something like li3_fixtures vs the li3 Mocker. If he was creating instead of the model instead that would be a different solution.

use app\models\customer\Mock as Customer;

Customer::applyFilter('find', function($self, $params, $chain) {
    return new Customer(array('first' => 'Blaine', 'last' => 'Schmeisser'));
});
$customer = Customer::find(...);
$this->assertIdentical('Blaine Schmeisser', $customer->fullName());

But then again if you just wanted to test instance methods you could just create a new Customer with data in it? From the app, you shouldn't need to test the find method, li3 tests that.

If you want to stub the model from the controller the controller would need the $_classes because you can't change what classes it uses from the parent like he was trying to do in his last example. When you extend or call (depending how you set it up) you'd overwrite it to use your static/dynamic mock class which at which point you could filter the find method like shown above.

In all, there are lots of ways to test what you are trying to test. It's all preference now.

@nateabele nateabele closed this Apr 14, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment