I'm trying to get started with PHPSpec and I've hit a wall. Things have gotten a bit convoluted in order to mock the proper things in the already-existing code I was given to work with, but in essence my question involves testing that something happened on an object which has just been created.
I have a RepositoryFactory that has createRepository(EntityManager $em, $entityName)
Doctrine's EntityManager::getRepository($entityName) just calls RepositoryFactory::getRepository(EntityManager $em, $entityName) and if the repository doesn't exist, THAT calls RepositoryFactory::createRepository(EntityManager $em, $entityName)
So, in my test, the repository is being mocked from RepositoryFactory::getRepository.
class MyEntityManagerSpec extends ObjectBehavior
{
function let(..., MyRepositoryFactory $rfact, MyEntityRepository $repo, ...)
{
....
$rfact->getRepository(Argument::any(), Argument::any())
->willReturn($repo);
...
}
function it_sets_a_field_on_repositories(MyEntityRepository $repo)
{
//This class calls its own getRepository, which calls
//getRepository on the factory, which ->willReturn($repo).
//So, effectively (but without mocking the SUT) that means
//$this->getRepository($entityName)->willReturn($repo)
$entityName = 'blah\blah\FakeEntity';
$repo->setField(Argument::any())->shouldBeCalled();
//The above fails with
//"No calls that match MyEntityRepository\P112->setField(*)"
$gotRepo = $this->getRepository($entityName);
$repo->setField(Argument::any())->shouldHaveBeenCalled();
//This fails in the same way
$gotRepo->shouldBe($repo);
//This test passes but doesn't let me verify the property was set
//and is therefore of little help to me
$gotRepo->getField()->shouldNotBeNull();
//I wanted to use shouldBe/shouldHaveBeenCalled but if the field's
//been set that's just as good. Except this fails as well, with
//"is_null(null) not expected to return true, but it did."
}
Now before the answers start coming about testing in isolation, I realize that. I first started trying to write the check for the field-setting in MyRepositoryFactory::createRepository but the same sort of problem cropped up-- if I'm making the object within createRepository, then I have no mocks to test against with shouldBe/shouldHaveBeenCalled. But I'm trying to do things right, here, so if this is the wrong place for my test, I'd rather refactor a lot than have a hacky test that passes.
EDIT: This is the actual bit being tested
class MyEntityManager
{
...
public function getRepository($entityName)
{
$repo = parent::getRepository($entityName);
$metaData = $this->getClassMetadata($entityName);
$flag = $this->getTarget() && $metaData->reflClass
->implementsInterface('Caj\Bundle\NameOfBundle\Model\NameOfBundleInterface');
if($flag) {
$repo->setField($this->getField());
}
return $repo;
}
}
The $repo in the above bit is what should be being mocked here; parent::getRepository ==> RepositoryFactory::getRepository ==> RepositoryFactory::createRepository
Additionally, I know that the test gets into the if($flag) block, but the code inside isn't working. $this->getField() works and returns properly, but $repo->setField still receives null. $repo->setField($field) is a normal setter with no funky logic.
Related
I have a group of Repository classes I'd like to test. All Eloquent methods are called in these classes, however I am unsure on how to proceed. The only thing that needs to be tested, is that the methods return the right data type. I want to be sure that getByEmail($email) for example actually returns a User object, but I don't care about the implementation details.
Every example I see tells you to inject the model in to the Repository class and mock it, then you can proceed by having that Mock expect very specific method calls. Is this the correct approach? Because it seems to me that would be forcing the developer to use very specific methods when building the Eloquent query.
Here's an example:
class UserRepository {
public function __construct(User user) { ... }
public function getByEmail(string $email) : ?User {
$this->user->where('email', $email)->first();
}
}
For the test with Mock to work I have to do:
class myTest {
public function testMethodReturnsUserObject() {
$user = Mockery::mock(User::class);
$repo = new UserRepository($user);
// now, if a developer changes the eloquent method calls, this will fail.
$user->shouldReceive('where->first')->andReturn($user);
$this->assertEquals($user, $repo->getByEmail('joe.bloggs#acme.com'));
}
}
Any ideas what I should do to test that my method does indeed return a User object, without forcing the developer to use exactly one where() method, and one first() method in this example. What if the developer needs to instead make it ->select()->where()->where()->firstOrFail() . Now the unit test will break, even though it really shouldn't care about this, and only that the method returns a User.
Thanks!
Skip unit testing this, and focus on integration tests with the database. Since that's your primary functionality you want to assert: "Does this repository return the record(s) I expect?"
public function testMethodReturnsUserObject(): void
{
// Note: old factory() syntax; Laravel 8 is different
$expectedUser = factory(User::class)->create([
'id' => 1,
'email' => 'user#example.com',
]);
$repo = new UserRepository(new User());
$actualUser = $repo->getByEmail('user#example.com');
self::assertNotEmpty($actualUser);
self::assertSame(1, $actualUser->id);
self::assertNull($repo->getByEmail('notfound#example.com'));
}
Lots of great docs to assist in database testing.
I am using Symfony and I'm trying to test the addStudentCard function in "Student" class, which adds a "StudentCard" object to $studentCards array collection propriety AND a "Student" object to $student propriety in "StudentCard" class. This is how I did it:
class StudentCard {
private $student;
public function getStudent();
public function setStudent();
//...
}
class Student {
private $studentCards;
public function getStudentCards();
public function addStudentCard(StudentCard $studentCard){
$studentCard->setStudent($this);
$this->studentCards[] = $studentCard;
return $this;
//...
}
What I want to achieve is to test this addStudentCard function using a MockBuilder, I have already done this without using mocks by doing:
class StudentTest extends AbstractTestCase {
public function testAddStudentCard(){
$studentCard = new StudentCard();
$student = new Student();
$student->addStudentCard($studentCard);
$student->assertSame($studentCard, $student->getStudentCards()[0]);
$student->assertSame($student, $studentCard->getStudent());
}
This works as expected with no problem.
What I would like is to replace the line:
$studentCard = new StudentCard();
with something like this:
$studentCard = $this->getMockBuilder(StudentCard::class)->getMock();
But what I get is the error:
Failed asserting that null is identical to an object of class Student.
The problem with your scenario is, that you are asserting that the mock returns the original student:
$student->assertSame($student, $studentCard->getStudent());
If the $studentCard is a Mock object, it doesn't return the original object unless you tell it to do so. But since you are already using a mock, there is no need to test that.
What you actually want to test in this case is, that the $student was assigned back to the $studentCard. That is what expectations are for.
So in your particular case you would go with:
$studentCard->expects($this->once())->method('setStudent')->with($student);
// ...
$student->addStudentCard($studentCard);
Make sure that you have the line there (as I shown in the code) before you call addStudentCard, otherwise the test will fail that the expectation has not been met.
After you set the expectations, there is no need to run any assertions (and you should not) on the mock objects.
The answer Ondrej Führer provided is the right answer to the problem I described.
I had also a removeStudentCard method that deletes the student from the studentCard object, so $this->once() was not appropriate for my case. In order to test this, I did exactly the same thing Ondrej Führer suggested with some modifications, so the code line I added was:
$studentCard->expects($this->exactly(2))->method('setStudent')->withConsecutive(
[$student],
[null]
);
//...
$student->addStudentCard($studentCard);
//...
$student->removeStudentCard($studentCard);
This is self explaining, the method setContact is expected to be called exaclty two times with $student as an argument for the first time, and null in the second call.
Hopefully this would be helpful for anyone looking to do something similar.
I'm developing unit tests on a project and I came across a class that does not contain constructor.
You may ask yourself, "How does this object exist then?"
Well, in another system, this object is generated and saved in database, so in the system where unit tests are required, there is no constructor defined for that class. But there is a need to test their functions so that if they are modified in the system in the future, the tests will indicate in which situations they are used, this will ensure me not to neglect the other points of the project that use it.
Within this environment, how can you test this type of class?
I tried using mocks, but mock brings all null attributes. In addition, a mocked object only comes as a result of a function that you say previously it will bring. So to test the functions of the object itself, this is not functional.
Any idea?
PS: Sorry for the bad english.
Constructors are optional. A class does not need a constructor for you to be able to instantiate it or test whether its methods are functioning correctly.
As I understand it, you want to test a method which behaves differently depending on a particular property, which would normally be set by a constructor but in your class isn't. That means that in the actual code usage, that property is probably being set directly at some point or there's a different method that sets its value.
In general, for testing these kinds of methods you should always set such a property yourself anyway. The reason for this is simple: a single test should only test one particular method. If you rely on the constructor, your test would be testing the combination of both the constructor and that method. Your test of the method will become dependent on the constructor behaving properly.
Imagine the following:
class Mood {
public $happy;
function __construct() {
$this->happy = true;
}
public function greet() {
if ($this->happy) {
return 'Hi!';
} else {
return 'Go away';
}
}
}
Let's say you want to test the behavior of the greet() method:
class MoodTest extends \PHPUnit\Framework\TestCase {
public function testGreet() {
$mood = new Mood();
$this->assertEqual('Hi!', $mood->greet())
}
}
This test would pass, because we assume the constructor is doing its job and is setting the $happy property to true. But there are two problems here:
We are not testing that the method works properly if $happy is false
We rely on the constructor to set $happy's initial value to true
This means things can change outside our control that would break this specific test, even though the function still works as expected. Business logic may change so that the constructor will set $happy to false initially. Or the developer's logic may change where the constructor disappears entirely and $happy is set in some other way (maybe a setHappy() method is introduced at some point).
To properly test the greet() method, there should be a test for every possible outcome. In this case the method has a single if statement, so there ought to be a test case for both outcomes of that condition:
class MoodTest extends \PHPUnit\Framework\TestCase {
public function testGreetIfHappy() {
$mood = new Mood();
$mood->happy = true;
$this->assertEqual('Hi!', $mood->greet())
}
public function testGreetIfNotHappy() {
$mood = new Mood();
$mood->happy = false;
$this->assertEqual('Go away', $mood->greet())
}
}
Now, no matter what happens to the constructor or business logic, these tests are testing only the behavior of the greet() method. Whatever else the constructor is doing (even if doesn't exist or does nothing at all) no longer has any effect on the tests of the greet() method.
I'm new to testing and writing testable code, and am looking for some clarification on the correct way to handle this simple scenario. I've read other questions and answers on SO with similar titles but they do not seem to offer a clear answer to what I'm asking.
I have a controller that calls the shipped() method on an instance of my Picking class:
class MyController extends \BaseController {
public function controllerMethod() {
$picking = new Picking;
$picking->shipped($shipmentData);
}
}
The Picking model looks like this:
class Picking extends \Eloquent {
public function order() {
return $this->belongsTo('Order');
}
public function shipped($shipmentData) {
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$this->order->pickingShipped();
}
}
As you can see, this shipped() method saves some data, and then calls the pickingShipped() method, on it's related Order.
Now, I am trying to write a test for the shipped() method, and I'm not sure the appropriate way to do this. I've read about mocking, but I am confused if this is a situation where mocking is necessary. I've thought of a few possible solutions, but I'm not sure if any of them are correct.
1) Rearrange the code so that the controller calls the pickingShipped() method allowing it to be removed from the shipped() method, simplifying the test.
For example, the last line of the shipped() method would be removed, and the controller code would change to:
$picking = new Picking;
$picking->shipped($shipmentData);
$picking->order->pickingShipped();
2) In the test, use a mock method on order so that the test can simply confirm that the pickingShipped() method gets called.
Something along the lines of what's explained here. That would mean the test could do something like this:
$order->expects($this->once())->method('pickingShipped')
However, I think that would mean that I also need to inject the order dependency rather than relying on the order relationship within the shipped() method, like this:
class Picking extends \Eloquent {
public function order() {
return $this->belongsTo('Order');
}
public function shipped(Order $order, $shipmentData) {
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$order->pickingShipped();
}
}
And then the code in the controller would have to look like this:
$picking = new Picking;
$picking->shipped($picking->order, $shipmentData);
This feels a little strange, but I'm really not sure what's right.
My question is, what is the proper way to write and test this code? It's easy to test the the shipped() method sets the appropriate data on itself, but what about that call to pickingShipped() at the end? This seems to make the testing more complicated. So should the code be rearranged? If so, how? Or, is this a common use-case for mocking like I outlined in the 2nd option? If so, is it correct to inject the dependency as I'm showing?
I'm not a PHP dev so this might come down to language features being a blocker.
I would suggest that the dependency injection method is better because it calls out the dependency and would allow you to separate your persistence and behavior later. For instance the Picking or Picker might be a better behavior name whilst PickingRecord might be nice for the data.
In any case if you can set default arguments in PHP then I like the last method you used (injection) and you could currently simplify to something like
public function shipped($shipmentData, Order $order = $this->order) {
$this->carrier = $shipmentData['Carrier'];
$this->service = $shipmentData['Service'];
$this->is_shipped = true;
$this->save();
$order->pickingShipped();
}
This then would allow you to ignore the order dependency in production code and inject a double or other type of object as an order in tests and simply assert that the method was called on the order object. Integration tests should continue to monitor that the interfaces still mesh together even though you're injecting doubles in your unit tests.
This would be how I'd attempt to do this in Ruby.
I came up with a solution that I feel good about. It seems pretty obvious now that I see it. All I did was set the $picking->order property to return the mocked order for the test.
$order = Mockery::mock(Order::class);
$picking = new Picking;
$picking->order = $order;
$order->shouldReceive('pickingShipped')
->with($picking)
->once();
$picking->shipped($shipmentData);
Now when the shipped() method calls $this->order, it gets the mocked $order object I defined, and the test works correctly.
This feels like the right solution.
I'm trying to get my head round Unit Testing and there's one more piece of the jigsaw I need to find.
What I'm trying to do is write tests for the following code. In this case, I've got a really simple Front Controller (written in PHP).
class frontController
{
public function routeRequest($oRequest)
{
$sClassname = $oRequest->getController();
$sMethod = $oRequest->getAction();
$oController = new $sClassname();
$oResponse = $oController->{$sMethod}($oRequest);
return $oResponse;
}
}
The problem I have is because the code creates new objects. I can easily mock the request object so that I can tightly control what it will actually do within my test case. I'm not sure the best way to actually replace the controller with a test double.
This article from IBM suggests having a factory method for creating my controller and then overriding this with a specific class used for testing:
class frontController
{
public function routeRequest($oRequest)
{
$sMethod = $oRequest->getAction();
$oController = $this->createController($oRequest);
$oResponse = $oController->{$sMethod}($oRequest);
return $oResponse;
}
protected function createController($oRequest)
{
$sClassname = $oRequest->getController();
return new $sClassname();
}
}
and then for testing perhaps something like this:
class testFrontController extends frontController
{
public function setMockController($oMockController)
{
$this->oMc = $oMockController;
}
protected function createController($oRequest)
{
return $this->oMockController;
}
}
(note this isn't quite what the article says, but I'm thinking it would be most useful to me if it did this)
Another solution could be to have another class that creates the controller. This would then be a dependent class of the frontController. This way I can replace the factory/creation class during testing with a test double. Something like this:
class frontController
{
public function routeRequest($oRequest, $oControllerFactory)
{
$sMethod = $oRequest->getAction();
$oController = $oControllerFactory->create($oRequest);
$oResponse = $oController->{$sMethod}($oRequest);
return $oResponse;
}
}
class controllerFactory
{
public function create($oRequest)
{
$sClassname = $oRequest->getController();
return new $sClassname();
}
}
I guess the dependency injection could be taken care of in the front controller constructor or via a setter instead of a parameter to the actual "route" method.
I think I prefer option 2.
Is either of these two methods the right way of going about testing this kind of thing?
(perhaps "good way" would be better word here!)
Any thoughts or suggestions on option 1 vs option 2 appreciated or indeed any alternatives. Remember - the key thing is about how to test an object that itself creates other objects as part of its execution.
Thanks!
You might find this article handy.
It discusses how object creation should be separated from the actual running of the application.
I generally find factories to be a good thing to use for this scenario. In addition to the swappability aspect, it means that additional parameters, data, or dependencies required by the object being created can be stored by the factory, and so the object which actually requests the new object doesn't have to know anything about them...
You do not want to use the real controller but a mock, right ?
It seems to me the simplest way to achieve this would be to subclass the request so that it returns the name of a MockController.
I assume you have thought through your assertions so as to define the goal of what exactly you are testing. Keep in mind that unit tests are going to be testing the returns from your methods, which, in this case, is $oResponse (whatever this may be). As a result, your test assertions will be based on this return value. Since I don't know what that return value is from your code snippets, I can only demonstrate an example that you can complete.
I would recommend PHPUnit for your testing as it seems to be the most complete package for PHP imho (many are fans of SimpleTest, as well ... to each their own).
It would look something like this (Please note that I have left out includes for brevity. Read the PHPUnit documentation for more information):
class AimTest extends PHPUnit_Framework_TestCase{
private $_controller = null;
private $_request = null;
public function setUp(){
$this->_controller = new frontController();
//what does this object's type?
$this->_request = new requestObject();
}
public function testObjectCreation(){
/*
* note, that this is only one of several assertions that could
* be made depending on the return value
*/
$return = $this->_controller->routeRequest($this->_request);
//tailor to what you expect your output to be
$this->assertTrue($return == "my expected output");
}
Hope I didn't miss the mark completely on your stated purpose. Moral of the story is that you can only test what your methods return. If you want to test object instantiation from a method, use the instanceof PHP function against a method that returns that object after instantiation.