PHPUnit and namespaces - mocked methods still being called - php

I am adding unit tests to an existing project that is using namespaces. I haven't ever had to use namespaces before, so it is somewhat of an adventure. My issue is that in my unit tests, it appears the mocked methods are still being called. Below is an example of the code file and the test.
private function selectFromDb($fields, $criteria = null) {
$fields = is_array($fields) ? implode(', ', $fields) : $fields;
$sql = "SELECT $fields FROM balloons";
if(!is_null($criteria)) {
$sql .= " WHERE $criteria";
}
$adapter = $this->getAdapter();
$statement = $adapter->query($sql);
$result = $statement->execute();
return $result;
}
Here is the test code:
// I'm passing in data here which isn't consequential for the question.
public function testSelectFromDb($fields, $criteria, $expectedSql) {
$statement = $this->getMockBuilder('Zend\Db\Adapter\Driver\Pdo\Statement')
->disableOriginalConstructor()
->setMethods(array('execute'))->getMock();
$statement->expects($this->once())
->method('execute')->will($this->returnValue('fake'));
$adapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter')
->disableOriginalConstructor()
->setMethods(array('query'))->getMock();
$adapter->expects($this->once())
->method('query')->with($expectedSql)
->will($this->returnValue($statement));
$bm = $this->getMockBuilder('Application\Model\BalloonModel')
->setMethods(array('getAdapter'))
->disableOriginalConstructor()->getMock();
$bm->expects($this->once())
->method('getAdapter')->will($this->returnValue($adapter));
// I use reflection as the method is private to the class
$reflection = new ReflectionClass($bm);
$method = $reflection->getMethod('selectFromDb');
$method->setAccessible(true);
$result = $method->invokeArgs($bm, array($fields, $criteria));
}
At this point, I'm just trying to get the test to execute to the end, but I continue to get the following error:
Tests\Model\BalloonModelTest::testSelectFromDb with data set "singleField" ('id', NULL, 'SELECT id FROM balloon')
Zend\Db\Adapter\Exception\InvalidQueryException: Statement could not be executed
/apath/PHP/vendor/zendframework/zendframework/library/zend/db/adapter/driver/pdo/statement.php:245
/apath/PHP/vendor/zendframework/zendframework/library/zend/db/adapter/driver/pdo/statement.php:240
/apath/PHP/module/Application/src/application/model/balloonmodel.php:243
/apath/PHP/tests/Model/BalloonModelTest.php:70
Caused by
PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'field list'
/apath/PHP/vendor/zendframework/zendframework/library/zend/db/adapter/driver/pdo/statement.php:240
/apath/PHP/module/Application/src/application/model/balloonmodel.php:243
/apath/PHP/tests/Model/BalloonModelTest.php:70
This tells me that the 'getAdapter', 'query' and 'execute' calls are still being made even though all of them are theoretically mocked. I've verified as best I can that the class names used are using the correct namespaces. Any ideas?

The problem is not in the namespaces. It's probably that you try to mock the class under test itself and I'm guessing that getAdapter() is a private method, called from within the class.
Remember that a mock is an object of a generated class that extends the original class.
Now, there's Mock_XYZ extends BalloonModel which adds the mock method getAdapter(), but if the getAdapter() method in BalloonModel itself is private, it will not be overridden but you end up with two different methods (one internal and one external if you will).
Solution
Refactor your code to use Dependency Injection. I'm not talking about IoC containers, just about creating other objects not in the class itself, but inject them with setters or constructor. Then you can do the following after creating the $adapter mock, instead of mocking getAdapter():
$bm->setAdapter($adapter);

Related

Mocking laravel models static call

Background
I have a system with a microservices setup. A few of these microservices run a laravel installation. In order to share some key models, a repo was shared using git/packagist.
Here is a diagram:
Microservice A
Microservice B
...
These both share Library C. This library has the shared models. This is outside of a normal laravel installation, but the composer includes "laravel/framework": "^9.0".
Note: There good external reasons to share the functionality - the microservices have come out of a monolith and are still developing fluidly and are not mature enough for a complete decoupling. This will come in time.
I wish to unit test these models.
Specifics
The requirement is that several models (User, Customer .. etc) all require addresses. Normalising these out would introduce complexity elsewhere that is not appropriate yet, so a trait is good for now. These have UK postcodes that require a specific validation against a database. Postcodes are modelled using a Postcode model.
I created a trait : AddressTrait. This offers some useful functionality. Included in this is a Postcode validation. This intercepts a set request in laravel (eg: $user->postcode = 'AB10 1AB)
/**
* Automatically updates the log/lat from the postcode
* #param $value
*/
public function setPostcodeAttribute($value): void
{
// update postcode
$this->attributes['postcode'] = strtoupper($value);
// now update lat/long
$postcode = Postcode::where('pcd', '=', str_replace(' ', '', $value))
->orWhere('pcd', '=', $value)
->first();
if ($postcode) {
$this->attributes['latitude'] = $postcode->latitude;
$this->attributes['longitude'] = $postcode->longitude;
}
}
This works as expected.
Note - it is to be extended quite a bit further with much more complexity, but this is step 1 and completely represents the problem.
Testing
If I interact with the postcode attribute, such as $user->postcode = 'AB10 1AB, this attempts to load the Postcode from the database, and the following error occurs:
Error : Call to a member function connection() on null
^ This is expected.
I would like to unit test this: ie. no reaching out the class and mocking system/functional elements. Thus, I need to mock the Postcode load (Postcode::where(..) .. ).
As this is a static call, I have used mockery ("mockery/mockery": "dev-master").
Here is the current attempt:
// ...
use Mockery;
use PHPUnit\Framework\TestCase;
// ...
public function testPostcodeProcessing(): void
{
$postcode_value = 'AB10 1AB';
$postcode_content = [
'pcd' => $postcode_value,
'latitude' => '0.1',
'longitude' => '0.2'
];
$mock_postcode = Mockery::mock(Postcode::class);
$mock_postcode->shouldReceive('where')->once()->andReturn($mock_postcode);
$mock_postcode->shouldReceive('orWhere')->once()->andReturn($mock_postcode);
$mock_postcode->shouldReceive('first')->once()->andReturn($postcode_content);
$model = $this->createTraitImplementedClass();
$model->postcode = $postcode_value;
}
protected function createTraitImplementedClass(): Model
{
return new class extends Model {
use AddressTrait;
};
}
TLDR question
I would like to unit test this function: ie. no reaching out the class and mocking.
How do I mock a laravel/eloquent static call, given that:
this is to be tested outside laravel
there is no database connection
OR
How do I refactor this to allow it to be more testable
Super TLDR;
How do I mock the load in:
public function tldr(): void
{
// this eloquent lookup needs to be mocked (not moved, refactored etc etc..)
$postcode = Postcode::where('pcd', '=', 'AB10 1AB')->first();
}
Notes:
These are unit tests
I would prefer to do this "the laravel way", but given the unusual circumstances things such as mockery might make sense
May be a gotcha: I am using the phpunit PHPUnit\Framework\TestCase - not the usual PHP test case. This is not a "requirement", but I imagined a mock shouldn't need the extended features.
Any help with this would be appreciated!
What if you abstracted away the part where you get the postcode?
public function setPostcodeAttribute($value): void
{
// update postcode
$this->attributes['postcode'] = strtoupper($value);
// now update lat/long
$postcode = $this->getPostCode($value);
if ($postcode) {
$this->attributes['latitude'] = $postcode->latitude;
$this->attributes['longitude'] = $postcode->longitude;
}
}
// you could make this method protected as well
// but if you do, your need to call the shouldAllowMockingProtectedMethods()
// when creating your mock
public function getPostCode(string $value): ?Postcode
{
return Postcode::where('pcd', '=', str_replace(' ', '', $value))
->orWhere('pcd', '=', $value)
->first();
}
If you do it like this, you no longer need to mock Eloquent Query builder at all. Partially mocking a class that uses that Address trait should give you what you need. I'm not sure if this works for anonymous classes though
public function test_existing_postcode()
{
// Arrange
$userMock = Mockery::mock(User::class)->makePartial();
$user = new User;
$postcode_value = 'AB10 1AB';
$postcode = new PostCode([
'pcd' => $postcode_value,
'latitude' => '0.1',
'longitude' => '0.2'
]);
// Expect
$userMock->expects()
->getPostCode($postcode_value)
->andReturn($postcode);
// Act
$user->postcode = $postcode_value;
// Assert
$this->assertEquals($user->latitude, $postcode->latitude);
$this->assertEquals($user->longitude, $postcode->longitude);
}
public function test_nonexisting_postcode()
{
// Arrange
$userMock = Mockery::mock(User::class)->makePartial();
$user = new User;
$postcode_value = 'AB10 1AB';
// Expect
$userMock->expects()
->getPostCode($postcode_value)
->andReturn(null);
// Act
$user->postcode = $postcode_value;
// Assert
$this->assertNull($user->latitude);
$this->assertNull($user->longitude);
}
Although I wouldn't recommend it, if you had a static method inside the Postcode model.
class Postcode extends Model
{
public static function getPostcodeByValue(string $value): ?Postcode
{
return Postcode::...
}
}
You could mock it with
$postcodeMock = \Mockery::mock('alias:Postcode');
$postcodeMock->shouldReceive('getPostcodeByValue')
->with($value)
->andReturn($postcode);
I'm not sure if expects() works, but if it does, you can also write this as
$postcodeMock = \Mockery::mock('alias:Postcode');
$postcodeMock->expects()
->getPostcodeByValue($value)
->andReturn($postcode);
Important: for this to work, the Postcode class should not have been loaded (by this or any previous tests). It's that fragile.
You can make your method more test friendly
Injectable external class to remove hidden dependencies
Keep the formatting/input validation outside if it is not related to "something" structural
Separate functionalities or the S in SOLID principles (move the lookup for Postcode instance to where it belongs)
like this
/**
* Automatically updates the log/lat from the postcode
* #param string $value
* #param Postcode $postcode
*/
public function setPostcodeAttribute($value, Postcode $postcode = null): void
{
// update postcode
$this->attributes['postcode'] = $value;
if ($postcode) {
$this->attributes['latitude'] = $postcode->latitude;
$this->attributes['longitude'] = $postcode->longitude;
}
}
After some extensive looking into this, I've found the answer using mockery aliases. This is done as follows:
Isolate this class/test from the remainder of the tests
If you create an alias, this overwrites the class globally for the rest of the current process. It's risky, but this can be done and many of the problems sidestepped by running the test/class in a separate process.
This can be done using the docblock:
/**
* At a class level
* #runTestsInSeparateProcesses
* #preserveGlobalState disabled
*/
Mock the class as an alias
Aliases mock static classes. This is the key point I was missing during my question - I missed the alias: part.
public function testPostcodeProcessing(): void
{
// define this first to intercept the global instantiation
$mock_postcode = Mockery::mock('alias:' . Postcode::class);
// ...
}
The above mock will override ALL Postcode classes in this test/test class. Thus, it should be declared first.
Add your responses and assertions
This is entirely up to you, but here is the example and assertions I created.
/*
* Tests that the postcode processes correctly.
*/
public function testPostcodeProcessing(): void
{
// define this first to intercept the global instantiation
$mock_postcode = Mockery::mock('alias:' . Postcode::class);
// set up a returned class
$returned_postcode = new Postcode();
$postcode_pcd = 'AB10 1AB';
$postcode_latitude = 0.1;
$postcode_longitude = 0.2;
$returned_postcode->pcd = $postcode_pcd;
$returned_postcode->latitude = $postcode_latitude;
$returned_postcode->longitude = $postcode_longitude;
// Set up the mock
$mock_postcode->shouldReceive('where')->once()->andReturn($mock_postcode);
$mock_postcode->shouldReceive('orWhere')->once()->andReturn($mock_postcode);
$mock_postcode->shouldReceive('first')->once()->andReturn($returned_postcode);
$model = $this->createTraitImplementedClass();
$model->postcode = $postcode_pcd;
$this->assertEquals($postcode_pcd, $model->postcode, 'The postcode object pcd was not set');
$this->assertEquals($postcode_latitude, $model->latitude, 'The postcode object latitude was not loaded');
$this->assertEquals($postcode_longitude, $model->longitude, 'The postcode object longitude was not loaded');
}
Note - these are "step 1" tests. The real class is more complex, and the test will be more complex. However, this gives the core solution to the instantiation issue.
TLDR;
Run this in a separate process
Use an Alias (and remember to declare it as an alias - alias:SomeClass)

How to access Doctrine defined methods after custom Repository is made?

I am developing a site using ZF2 and Doctrine. The problem I am facing is I am using Doctrine predefined object methods like findAll(), findOneBy(), findBy() etc in my code. For some custom actions I have prepared a custom Repository for one of my entities. Now I can't access the predefined methods. I have already written code by using findAll() method. But after building a repository I can't simply access findAll() method. How can I both access my custom defined methods along with Doctrine defined methods?
For example:
I am using findOneBy() like this:
$udata = $this->em()->getRepository('Application\Entity\Usermain')->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
Now I have prepared UsermainRepository like below:
namespace Application\Entity\Repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectRepository;
class UsermainRepository extends EntityRepository
{
protected $sl;
public function __construct($sl){
$this->sl = $sl;
}
public function customFind($arr)
{
$qb = $this->sl->createQueryBuilder();
$whereStr = '';
if(count($arr)){
foreach($arr as $kvarr=>$varr){
$whereStr .= "u.$kvarr = '".$varr."'";
}
}
$qry = $qb->select('u')
->from('Application\Entity\Usermain','u')
->where($whereStr)
->getQuery()
->getResult();
return $qry;
}
}
Now I can access
$udata = $this->em()->getRepository('Application\Entity\Usermain')->customFind(array('userEmail' => 'subh.laha#gmail.com'));
But Not
$udata = $this->em()->getRepository('Application\Entity\Usermain')->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
Why? I have already written code by using doctrine defined methods. What can I do now?
I believe you are getting this error because you have overridden the repository's constructor method but aren't calling the parent constructor so required parameters aren't being properly set.
I think your code is not correct, You can use the below query instead of writing complex custom object.
$query = $this->getEntityManager()->createQueryBuilder()
->select('U.id,U.name')
->from('Application\Entity\StudentClass', 'U')
->where('U.pkStudentClass = :pkStudentClass')
->setParameter('pkStudentClass', 1)
->setMaxResults(20);
->orderBy('id', 'DESC')
->getQuery();
$result = $query->getScalarResult();
There are several problems exist in your approach;
I think trying to access the service locator's itself in an entity repository is bad idea. You shouldn't need service container in repository level.
The second detail is, when extending any class, you need to check out, read and respect the signature of the parent. In your case, you're overriding the parent's __construct. Calling parent::__construct() may seems like a solution but it's not. You'll soon realize that you also need a custom repository factory to pass additional arguments to constructor while keeping the current functionality. No way.
This is more important than others: you believe that $this->sl->createQueryBuilder() returns query builder instance. Theoretically seems like working but $this->sl is not service locator, service locator doesn't knows anything about query builders, it's just EntityManager instance which passed to your constructor.
Try this:
<?php
namespace Application\Entity\Repositories;
use Doctrine\ORM\EntityRepository;
class UsermainRepository extends EntityRepository
{
public function customFind($arr)
{
// Just pass an alias for your entity
$qb = $this->createQueryBuilder('u');
$whereStr = '';
if (count($arr)) {
foreach ($arr as $kvarr => $varr) {
$whereStr .= "u.$kvarr = '".$varr."'";
}
}
return $qb->where($whereStr)
->getQuery()
->getResult();
}
}
Finally, in your Application\Entity\Usermain entity, you'll also need telling about your custom repository to doctrine since you don't want to use default EntityRepository :
namespace Application\Entity;
/**
* #ORM\Entity(repositoryClass="Application\Entity\Repositories\UsermainRepository")
*/
class Usermain
{
}
Now in your controller (or service) level, you can test:
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
$repo = $em->getRepository('Application\Entity\Usermain');
// repo is a UsermainRepository instance
// this should work:
$udata = $repo->customFind(array('userEmail' => 'subh.laha#gmail.com'));
// this should also work
$udata = $repo->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
I strongly recommend carefully reading of Working With Objects section of the documentation before diving into deeps.

SimpleTest Mock objects: clearing expectations

The short question: Is there a way to reset a Mock object in SimpleTest, removing all expectations?
The longer explanation:
I have a class that I'm testing using SimpleTest and am having some problem with the Mock objects it is using.
The class is a Logger, and inside the logger are a number of Writer objects (FileWriter, EmailWriter, etc). Calling the Logger::log() method performs some logic behind the scenes and routes the message to the correct writer. Writers are cached in the Logger class to save re-instantiating each one each time.
In my unit tests, I set up a Logger, create and add some Mock Writer objects to it and then have been using methods like MockDBWriter->expectOnce() to test that the Logger is working.
The problem now is that I want to test another function of the Logger, but the expectOnce expectations are still in effect and causing my subsequent tests to fail.
function testWritesMessageOK() {
$log = Logger::getInstance();
$mock = new MockFileWriter($this);
$log->addWriter($mock);
$mock->expectOnce("write", "Message");
$log->write("Message"); // OK
}
// this is just an example - the actual test is much less inane
function testNumberOfWrites() {
$log = Logger::getInstance();
$mock = $log->getWriter();
$mock->expectCallCount('write', 2);
$log->write("One"); // fail - it doesn't match "Message"
$log->write("Two");
}
Is there a way to reset a Mock object, removing all expectations?
Use separate mock instances.
Either:
$mock = $log->getWriter();
$mock = new $mock;
Or:
$mock = new MockFileWriter($this);
// And then:
$mock = new MockDBWriter($this);
// And then:
$mock = new MockEmailWriter($this);
// etc.
I'd question the wisdom of caching writers to save re-instantiation. If you make instantiation a cheap operation (i.e. don't create DB connection or anything) and defer that sort of thing until you actually need the connection, such as the first query, then you won't need to cache and this whole problem might go away.
The other thing you can do is call the SimpleMock constructor.
$mock = $log->getWriter();
$mock->SimpleMock();
Which will do all this:
/**
* Creates an empty action list and expectation list.
* All call counts are set to zero.
* #access public
*/
function SimpleMock() {
$this->_actions = &new SimpleCallSchedule();
$this->_expectations = &new SimpleCallSchedule();
$this->_call_counts = array();
$this->_expected_counts = array();
$this->_max_counts = array();
$this->_expected_args = array();
$this->_expected_args_at = array();
$test = &$this->_getCurrentTestCase();
$test->tell($this);
}
The only problem with that is that tell() call at the end which will cause the SimpleMock::atTestEnd() to be called twice when tallying up the expectations. But, you could fix that with this:
// $this should == the test case in question
array_pop($this->_observers);
This answer is based on version 1.0.1 of SimpleTest.

How to set the default hydrator in Doctrine?

I can't find a way to set the default hydrator in Doctrine. It should be available. Right?
http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/data-hydrators.html#writing-hydration-method
The above documentation page explains how to create a custom hydrator. The drawback here is that you need to "specify" the hydrator each and every time you execute a query.
I figured this out by reading Chris Gutierrez's comment and changing some stuff.
First, define an extension class for Doctrine_Query. Extend the constructor to define your own hydration mode.
class App_Doctrine_Query extends Doctrine_Query
{
public function __construct(Doctrine_Connection $connection = null,
Doctrine_Hydrator_Abstract $hydrator = null)
{
parent::__construct($connection, $hydrator);
if ($hydrator === null) {
$this->setHydrationMode(Doctrine::HYDRATE_ARRAY); // I use this one the most
}
}
}
Then, in your bootstrap, tell Doctrine about your new class.
Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_QUERY_CLASS, 'App_Doctrine_Query');
Chris Gutierrez defined the attribute for the connection instead of globally but I have more than one connection and I want to use this default for all of them.
Now you don't have to call Doctrine_Query::setHydrationMode() every time you build a query.
Here's more information
http://www.doctrine-project.org/projects/orm/1.2/docs/manual/configuration/en#configure-query-class
EDIT: Changes below
I have found a problem with the above. Specifically, doing something like "Doctrine_Core::getTable('Model')->find(1)" will always return a hydrated array, not an object. So I have altered this a bit, defining custom execute methods for use in a Query call.
Also, I added memory freeing code.
class App_Doctrine_Query extends Doctrine_Query
{
public function rows($params = array(), $hydrationMode = null)
{
if ($hydrationMode === null)
$hydrationMode = Doctrine_Core::HYDRATE_ARRAY;
$results = parent::execute($params, $hydrationMode);
$this->free(true);
return $results;
}
public function row($params = array(), $hydrationMode = null)
{
if ($hydrationMode === null)
$hydrationMode = Doctrine_Core::HYDRATE_ARRAY;
$results = parent::fetchOne($params, $hydrationMode);
$this->free(true);
return $results;
}
}
That'd be a great idea, and on reading your question I thought it'd be something you could do via Doctrine. However, reading through the code makes me think you can't:
Doctrine_Query::create() creates a new query specifying only the first argument of Doctrine_Query_Abstract::__construct(), the connection, without specifying the second argument - the hydration mode. No calls to configuration are made. As no hydrator is passed, a new Doctrine_Hydrator is created, and its constructor equally does not look anywhere for a configuration option, and thus it has the default Doctrine::HYDRATE_RECORD setting.
Perhaps subclassing Doctrine_Query with the below factory method is the easiest option?
public static function create($conn = null)
{
return new Doctrine_Query($conn,Doctrine::HYDRATE_ARRAY);
}

Testing an abstract method of a child-class from an abstract class

To stay with the same example I used here:
I now want to test the implementation of the protected methods in my child-classes.
Because I stub them in my test of the abstract class, the implementations themselves aren't tested.
But a protected-method isn't tested normally, so that's why I'd like your suggestions on how to test them after all.
Just like my other thread I'd like to solve this without refactoring my code.
Parent-class:
abstract class Order
{
public function __construct( $orderId, User $user )
{
$this->id = $this->findOrderId( $user->getId(), $orderId );
if ($this->id !== false) {
$this->setOrderData();
}
}
abstract protected function findOrderId( $userId, $orderIdToSearch );
private function setOrderData()
{
...
}
}
Child-class to test:
public class OrderTypeA extends Order
{
protected function findOrderId($userId, $orderId)
{
...
}
}
Test code:
class OrderTypeATest extends PHPUnit_Framework_TestCase
{
public function testFindOrderId() {
???
}
}
You can test protected/private methods using the reflection. Read this tutorial. There you will find, among the other solutions, the direct one:
/**
* Call protected/private method of a class.
*
* #param object &$object Instantiated object that we will run method on.
* #param string $methodName Method name to call
* #param array $parameters Array of parameters to pass into method.
*
* #return mixed Method return.
*/
public function invokeMethod(&$object, $methodName, array $parameters = array())
{
$reflection = new \ReflectionClass(get_class($object));
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
Also, regarding the previous question of yours, where you are trying to test abstract class. The solution with phpunit mocking must work. But if you use PHP 7, you can use Anonymous classes to achieve the same result:
abstract class Order
{
protected $id;
public function __construct($orderId, $userId)
{
$this->id = $this->findOrderId($userId, $orderId);
if ($this->id !== false) {
$this->setOrderData();
}
}
abstract protected function findOrderId($userId, $orderIdToSearch);
private function setOrderData()
{
echo 'setOrderData';
}
}
$orderId = 1;
$userId = 1;
$order = new class($orderId, $userId) extends Order {
protected function findOrderId($userId, $orderIdToSearch)
{
return 1;
}
};
You will end up with the working $order object, which is ready for testing. Also it is good idea to put this code in the setUp() method of the Test Case.
If you are only get a valid $this->id when a order is found right. Do some like:
$order = new OrderTypeA($orderId, $user);
$this->assertNotEquals(false,$order->id);
Or if $orderId equals $this->id
$order = new OrderTypeA($orderId, $user);
$this->assertEquals($orderId,$order->id);
But not enough code/logic shown here to tell you more;)
Your abstraction does not make sense to me.
I understand that you have an object representing an order. You instantiate it by giving a user and an order id. However there's more than one type of order, and the difference between these types of orders is in the way you search them in the database storage? That doesn't sound right.
Your code does tell a weird story. You have this order id, and the first thing you do is search the order id. I just thought that you already HAVE the order id, so there shouldn't be a need to yet again search for it. Or maybe that method has the wrong name, and instead of findOrderId() it should be called findOrderById() - or findUserOrderById().
Also, you do work in the constructor. Searching for stuff should not be done there.
Your testing problem comes from the fact that you decided to implement different search strategies as an abstract method. You have to test a protected abstract method, which is not really easy. It makes it also hard to property test the main abstract order class because you have to provide an implementation - and this implementation sounds like it conceals a database access layer, so there can be plenty of things going wrong in the real code.
I suggest not allowing the order to search itself. Searching for orders should be done outside of the order object. That way, you'll likely implement that search as a public method, which can be normally tested. The code searching for orders will then decide whether you have a successfully found OrderTypeA, or maybe a missing MissingOrderTypeA, both extending the order class. The order objects should carry the order data, not the search logic to find them in the database.
Hint: If you have problems testing your code, it is 99,9% likely that your code is trying to do things the wrong way. This is not saying that things cannot be done that way, it is saying that you are about to produce hard to test code, that is also hard to maintain, and that it is a good idea to look for alternative strategies to implement the solution. Elegant code is always easy to test, because all the necessary methods are public in the relevant classes, and therefore can work together as intended.

Categories