I want to make my controller thin and to separate business-logic from other operations. For example I have an action:
public function indexAction()
{
$languages = $this ->getEntityManager()
->getRepository('\ApanelLanguage\Entity\LanguageCommon')
->getLanguagesList();
$viewModel = new ViewModel(['languages' => $languages]);
return $viewModel;
}
but I want to get action like this:
public function indexAction()
{
$model = $new LanguageModel();
$model->getLanguagesList();
return $viewModel;
}
Is it possible to do? What must I have in Language/Model/LanguageModel ?
Thank you
Removing the business logic from your controller is a great idea for code reuse and maintainability; however I would recommend against moving the logic to your models. A better solution would be to add a service layer to your application.
What is a service layer? Martin Fowler describes it as the following:
[A service layer] defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation.
This essentially means that we add a class in-between your controller and your model.
The great advantage of this approach is that should you need to update the business logic of your application there is no need to update the controller. The controller also becomes unaware of any specific code and therefore can be reusable in other unrelated projects.
This 'service' could have a simple API, for example:
interface ServiceInterface
{
public function setObjectManager($objectManager);
public function setRepository($respository);
public function find($id);
public function fetchRow($criteria);
public function fetchAll($criteria);
public function insert($object);
public function update($object);
public function delete($object);
}
Then you can implement this interface for your new 'LanguageService'.
class LanguageService implements ServiceInterface
{
// ... all methods from interface
public function getLanguageList()
{
return $this->repository->getLanguagesList();
}
}
Lastly update your controller to use the new service
class FooController extends AbstractActionController
{
protected $languageService;
public function __construct(ServiceInterface $languageService)
{
$this->languageService = $languageService;
}
public function indexAction()
{
$languages = $this->languageService->getLanguageList();
$viewModel = new ViewModel(['languages' => $languages]);
return $viewModel;
}
public function insertAction()
{
$request = $this->getRequest();
$service = $this->languageService;
$form = $service->getInsertForm();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
// if our form used the DoctrineObjectHydrator
// we will get a entity back populated with the
// form data
$language = $service->insert($form->getData());
if ($language instanceof Entity\Language) {
// success
} else {
// failure
}
}
}
//
}
}
Related
I have class Name Validator and it has a method forVote.
This is my code.
public function test_should_set_default()
{
$this->mock = \Mockery::mock(Validator::class);
$this->mock->shouldReceive('forVote')
->andReturnTrue();
$this->app->instance(Validator::class,$this->mock);
$factory = new Factory();
$this->assertTrue($factory->setDefault());
}
So Factory calls Processor which calls Validator. Now I want mock validator to run. But it calls the real method.
What am I doing wrong?
https://laravel.com/docs/5.6/container#introduction
since the repository is injected, we are able to easily swap it out
with another implementation. We are also able to easily "mock", or
create a dummy implementation of the UserRepository when testing our
application.
My guess is you are perhaps currently instantiating your dependencies like so:
$processor = new Processor() and $validator = Validator::make(...);
So, in order to have your mocked class be used, you should use Dependency injection which just means your classes should inject your dependencies via the __construct method.
Your Factory class should be like:
class Factory {
$processor;
public function __construct(Processor $processor)
{
$this->processor = $processor;
}
public function setDefault()
{
$this->processor->callingValidator();
}
}
and your Processor to be like:
class Processor {
$validator;
/**
* The Validator will resolve to your mocked class.
*
*/
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function callingValidator()
{
$this->validator->make();
}
}
If we have the following class that uses the Builder design pattern:
class CourseListingBuilder extends Component
{
/**
* #var yii\db\Query
*/
private $query;
private $data = [];
public function init()
{
parent::init();
$this->query = new yii\db\Query();
}
/**
* Return a new instance of the builder
* #return CourseListingBuilder
*/
public static function create()
{
return new CourseListingBuilder();
}
public function selectColumns(array $columns)
{
// #TODO validate and format $columns
$this->query->select($columns);
return $this;
}
public function applyFilters($filters = [])
{
// #TODO validate and parse filters
$this->query->andWhere($filters);
return $this;
}
public function build()
{
// Make the actual DB query
$this->data = $this->query->all();
}
public function getData()
{
return $this->data;
}
}
We use it like this:
$data = CourseListingBuilder::create()
->selectColumns(['id', 'name'])
->applyFilters(['active'=>1])
->build()
->getData();
In time, these classes grow quite large because we have multiple developers working on them. Developers extend the selectColumns() or applyFilters() and occasionally, the code breaks. We've recently started introducing Unit Tests in the company and we would like to make classes that follow the above pattern - unit testable, preferrably through a small refactoring.
How do you properly unit test the above builder design pattern class, given its internal dependency to yii\db\Query (which comes from the Yii2 framework by the way, but this is not relevant to the example). What's relevant to the example is that we DO NOT want to test the internal behaviors of yii\db\Query. It comes from the framework. We know that it's working. In other words - we want to "mock" it as much as possible, while effectively testing the actual methods inside our Builder class and how they will affect the outcome.
The second thing to point out is that we know how to write unit tests in general. This question is not about "how to write unit tests" in general, but "how to write unit tests for a builder class that has an internal dependency to a third party DAO class".
Are we doing something wrong?
Such code is not unit testable but instead goes in the integration tests territory?
The simplest way would be to replace yii\db\Query with some simple class and test its state after method call.
class MockQuery extends \yii\db\Query {
public $select;
public $selectOption;
public function select($columns, $option = null) {
$this->select = $columns;
$this->selectOption = $option;
}
// ...
}
class CourseListingBuilder extends \yii\base\Component {
private $query;
public function selectColumns(array $columns) {
// #TODO validate and format $columns
$this->query->select($columns);
return $this;
}
// ...
}
And test:
public function testQuery() {
$builder = new CourseListingBuilder();
// use reflection to access private property
$reflection = new \ReflectionObject($builder);
$property = $reflection->getProperty('query');
$property->setAccessible(true);
$property->setValue($builder, new MockQuery());
$builder->selectColumns(['id', 'name']);
$query = $property->getValue($builder);
$this->assertSame(['id', 'name'], $query->select);
$this->assertNull($query->selectOption);
}
I am working on a unit test of an instance method. The method happens to be an ASP.NET MVC 4 controller action, but I don't think that really matters much. We just found a bug in this method, and I'd like to use TDD to fix the bug and make sure it doesn't come back.
The method under test calls a service which returns an object. It then calls an internal method passing a string property of this object. The bug is that under some circumstances, the service returns null, causing the method under test to throw a NullReferenceException.
The controller uses dependency injection, so I have been able to mock the service client to have it return a null object. The problem is that I want to change the method under test so that when the service returns null, the internal method should be called with a default string value.
The only way I could think to do this is to use a mock for the class under test. I want to be able to assert, or Verify that this internal method has been called with the correct default value. When I try this, I get a MockException stating that the invocation was not performed on the mock. Yet I was able to debug the code and see the internal method being called, with the correct parameters.
What's the right way to prove that the method under test calls another method passing a particular parameter value?
I think there's a code smell here. The first question I'll ask myself in such a situation is, is the "internal" method really internal/ private to the controller under test. Is it the controller's responsibility to do the "internal" task? Should the controller change when the internal method's implementation changes? May be not.
In that case, I would pull out a new targeted class, which has a public method which does the stuff which was until now internal to the controller.
With this refactoring in place, I would use the callback mechanism of MOQ and assert the argument value.
So eventually, you will end up mocking two dependancies:
1. The external service
2. The new targeted class which has the controller's internal implementation
Now your controller is completely isolated and can be unit tested independently. Also, the "internal" implementation becomes unit testable and should have its own set of unit tests too.
So your code and test would look something like this:
public class ControllerUnderTest
{
private IExternalService Service { get; set; }
private NewFocusedClass NewFocusedClass { get; set; }
const string DefaultValue = "DefaultValue";
public ControllerUnderTest(IExternalService service, NewFocusedClass newFocusedClass)
{
Service = service;
NewFocusedClass = newFocusedClass;
}
public void MethodUnderTest()
{
var returnedValue = Service.ExternalMethod();
string valueToBePassed;
if (returnedValue == null)
{
valueToBePassed = DefaultValue;
}
else
{
valueToBePassed = returnedValue.StringProperty;
}
NewFocusedClass.FocusedBehvaior(valueToBePassed);
}
}
public interface IExternalService
{
ReturnClass ExternalMethod();
}
public class NewFocusedClass
{
public virtual void FocusedBehvaior(string param)
{
}
}
public class ReturnClass
{
public string StringProperty { get; set; }
}
[TestClass]
public class ControllerTests
{
[TestMethod]
public void TestMethod()
{
//Given
var mockService = new Mock<IExternalService>();
mockService.Setup(s => s.ExternalMethod()).Returns((ReturnClass)null);
var mockFocusedClass = new Mock<NewFocusedClass>();
var actualParam = string.Empty;
mockFocusedClass.Setup(x => x.FocusedBehvaior(It.IsAny<string>())).Callback<string>(param => actualParam = param);
//when
var controller = new ControllerUnderTest(mockService.Object, mockFocusedClass.Object);
controller.MethodUnderTest();
//then
Assert.AreEqual("DefaultValue", actualParam);
}
}
Edit: Based on the suggestion in the comments to use "verify" instead of callback.
Easier way to verify the parameter value is by using strict MOQ behavior and a verify call on the mock after system under test is executed.
Modified test could look like below:
[TestMethod]
public void TestMethod()
{
//Given
var mockService = new Mock<IExternalService>();
mockService.Setup(s => s.ExternalMethod()).Returns((ReturnClass)null);
var mockFocusedClass = new Mock<NewFocusedClass>(MockBehavior.Strict);
mockFocusedClass.Setup(x => x.FocusedBehvaior(It.Is<string>(s => s == "DefaultValue")));
//When
var controller = new ControllerUnderTest(mockService.Object, mockFocusedClass.Object);
controller.MethodUnderTest();
//Then
mockFocusedClass.Verify();
}
"The only way I could think to do this is to use a mock for the class under test."
I think you should not mock class under test. Mock only external dependencies your class under test has. What you could do is to create a testable-class. It would be a class which derives from your CUT and here you can catch the calls to the another method and verify it's parameter later. HTH
Testable class in the example is named MyTestableController
Another method is named InternalMethod.
Short example:
[TestClass]
public class Tests
{
[TestMethod]
public void MethodUnderTest_WhenServiceReturnsNull_CallsInternalMethodWithDefault()
{
// Arrange
Mock<IService> serviceStub = new Mock<IService>();
serviceStub.Setup(s => s.ServiceCall()).Returns((ReturnedFromService)null);
MyTestableController testedController = new MyTestableController(serviceStub.Object)
{
FakeInternalMethod = true
};
// Act
testedController.MethodUnderTest();
// Assert
Assert.AreEqual(testedController.SomeDefaultValue, testedController.FakeInternalMethodWasCalledWithThisParameter);
}
private class MyTestableController
: MyController
{
public bool FakeInternalMethod { get; set; }
public string FakeInternalMethodWasCalledWithThisParameter { get; set; }
public MyTestableController(IService service)
: base(service)
{ }
internal override void InternalMethod(string someProperty)
{
if (FakeInternalMethod)
FakeInternalMethodWasCalledWithThisParameter = someProperty;
else
base.InternalMethod(someProperty);
}
}
}
The CUT could look something like this:
public class MyController : Controller
{
private readonly IService _service;
public MyController(IService service)
{
_service = service;
}
public virtual string SomeDefaultValue { get { return "SomeDefaultValue"; }}
public EmptyResult MethodUnderTest()
{
// We just found a bug in this method ...
// The method under test calls a service which returns an object.
ReturnedFromService fromService = _service.ServiceCall();
// It then calls an internal method passing a string property of this object
string someStringProperty = fromService == null
? SomeDefaultValue
: fromService.SomeProperty;
InternalMethod(someStringProperty);
return new EmptyResult();
}
internal virtual void InternalMethod(string someProperty)
{
throw new NotImplementedException();
}
}
I am trying to set up the simplest of tests in my controller but, as with most things Laravel, there are no decent tutorials to demonstrate the simple stuff.
I can run a simple test (in a file called UserControllerTest) like this:
public function testIndex()
{
$this->call('GET', 'users');
$this->assertViewHas('users');
}
This calls the /users route and passes in an array users.
I want to do the same with Mockery but how?
If I try this:
public function testIndex()
{
$this->mock->shouldReceive('users')->once();
$this->call('GET', 'users');
}
I get an error that "Static method Mockery_0_users::all does not exist on this mock object.
Why not? I am mocking User which extends Ardent and in turn extends Eloquent. Why does ::all not exist for the mock?
BTW, these are the set-up functions for Mockery:
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('User');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
You can't directly mock an Eloquent class. Eloquent is not a Facade and your User model neither. There is a bit of magic in Laravel but you can't do things like that.
If you want to mock your User class, you have to inject it in the controller constructor. The repository pattern is a good approach if you want to do that. There is a lot of articles about this pattern and Laravel on Google.
Here some pieces of code to show you how it could look like :
class UserController extends BaseController {
public function __construct(UserRepositoryInterface $users)
{
$this->users = $users;
}
public function index()
{
$users = $this->users->all();
return View::make('user.index', compact('users'));
}
}
class UserControllerTest extends TestCase
{
public function testIndex()
{
$repository = m::mock('UserRepositoryInterface');
$repository->shouldReceive('all')->andReturn(new Collection(array(new User, new User)));
App::instance('UserRepositoryInterface', $repository);
$this->call('GET', 'users');
}
}
If it seems to be too much structuration for your project you can just call a real database in your tests and don't mock your model classes... In a classic project, it just works fine.
This function is part of a project called apiato.io you can use it to mock any class in Laravel, even facade, basically anything that can be resolved with the IoC, which is almost all classes if you are using proper dependency injection:
/**
* Mocking helper
*
* #param $class
*
* #return \Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
App::instance($class, $mock);
return $mock;
}
Let's say I have a small class that handles connections to MySQL databases:
class myDatabaseHandler{
private $_databases;
public function addDatabase($name, $dsn, $username, $password){
$this->_databases[$name] = array('dsn' => $dsn, 'username' => $username, 'password' => $password);
return true;
}
public function getDatabase($name){
$connectionInfo = $this->_databases[$name];
$database = new Database($connectionInfo['dsn'], $connectionInfo['username'], $connectionInfo['password']);
$database->doSomeSetup();
$database->attachMoreThings();
return $database;
}
}
I want to unit test these 2 methods:
class myDatabaseHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testAddDatabase(){
}
public function testGetDatabase(){
}
}
How can this I test those 2 methods? If I addDatabase(), at most it would return a Boolean telling me the operation succeeded. Since it writes to a private property, I cannot confirm that the correct data is indeed written to it.
I feel that using getDatabase() to get a Database object back and testing against it is not exactly ideal, because I would need to expose dsn, username and password just for the sake of testing. In addition, it is possible that the Database object might modify those values to a format it uses, so I need to store the original values just for testing.
What is the best way to approach this problem?
Testing certainly gets tricky when you try to construct and use an object in the same place. In this case, you are both constructing a Database and calling methods on it in your getDatabase($name) method. That makes it pretty much impossible to mock, for instance, and to get decent coverage your tests would need to test the functionality provided by the Database class to make sure the system was behaving as expected.
A better way might be using a proper factory as a dependency.
interface iDatabaseFactory
{
public function buildDatabase($dsn, $username, $password);
}
Then, you could mock both the database factory and the database instance itself to verify that it is both constructed correctly and initialized correctly:
class MockDatabaseFactory implements iDatabaseFactory
{
public $databaseParams = array();
public $databaseToReturn = NULL;
public function buildDatabase($dsn, $username, $password)
{
$this->databaseParams['dsn'] = $dsn;
$this->databaseParams['username'] = $username;
$this->databaseParams['password'] = $password;
return $this->databaseToReturn;
}
}
class myDatabaseHandlerTest extends PHPUnit_Framework_TestCase
{
public function testAddAndGetDatabaseUsesCorrectDbParameters(){
$mockDatabaseFactory = new MockDatabaseFactory();
$dbHandler = new myDatabaseHandler($mockDatabaseFactory);
// implement MockDatabase according to your interface
$mockDatabase = new MockDatabase();
$mockDatabaseFactory->databaseToReturn = $mockDatabase;
$dbHandler.addDatabase("some name", "some dsn",
"some username", "pa$$w0rd");
$builtDatabase = $dbHandler.getDatabase("some name");
$this->assertEquals($mockDatabase, $builtDatabase);
$dbParams = $mockDatabaseFactory->databaseParams;
$this->assertEquals("some dsn", $dbParams['dsn']);
$this->assertEquals("some username", $dbParams['username']);
$this->assertEquals("pa$$w0rd", $dbParams['password']);
}
public function testAddAndGetDatabaseInitializesDb(){
$mockDatabaseFactory = new MockDatabaseFactory();
$dbHandler = new myDatabaseHandler($mockDatabaseFactory);
$mockDatabase = new MockDatabase();
$mockDatabaseFactory.setDatabaseToBuild($mockDatabase);
$dbHandler.addDatabase("name", "dsn", "user", "pass");
$builtDatabase = $dbHandler.getDatabase("some name");
$this->assertTrue($mockDatabase->doSomeSetupWasCalled);
$this->assertTrue($mockDatabase->attachMoreThingsWasCalled);
}
}