Yii2 + Codeception: How to use fixtures? - unit-testing

I wrote a simple test for my Yii2 application using Codeception. Instead of using the real MySQL db, I want to use fixtures.
Here is the code:
tests/PersonTest.php:
namespace app\tests\unit\models;
use tests\fixtures;
use app\controllers;
class PersonTest extends \Codeception\Test\Unit
{
protected $tester;
public $appConfig = '#app/config/main.php';
protected function _before(){ }
protected function _after(){ }
public function _fixtures()
{
return [ 'Person' => fixtures\PersonFixture::className() ];
}
public function testUser(){
$person = Person::findOne( [ "id" => 1 ] );
$userId = isset( $person->id ) ? $person->id : false;
$this->assertEquals( 1, $userId );
}
}
tests/fixtures/data/Person.php
return [
'person1' => [
'id' => 1,
'firstname' => 'Foo',
'lastname' => 'Bar',
],
];
tests/fixtures/Person.php
namespace tests\fixtures;
use yii\test\ActiveFixture;
class PersonFixture extends ActiveFixture
{
public $modelClass = 'app\models\Person';
}
When I run the test, I just get the error:
[Error] Class 'tests\fixtures\PersonFixture' not found
I tried 100 different things, but I can not make it work. If this simple example would work for me, I could create real tests.

With Codeception 2.3.8 you can do it like this:
Define your fixture (and have the data file just like you have in your question)
namespace app\tests\fixtures;
class PersonFixture extends \yii\test\ActiveFixture {
public $modelClass = 'app\models\Person';
}
And write your test
namespace app\tests\unit;
class PersonTest extends \Codeception\Test\Unit {
public function _fixtures() {
return [
'persons' => 'app\tests\fixtures\PersonFixture',
];
}
public function testUser() {
$person1 = $this->tester->grabFixture('persons', 'person1');
$this->assertEquals(1, $person1->id);
}
}
That's it.

with codeception 4.0.3 you can run your fixture by following the steps...
create fixtures folder inside test
[fixture folder][1]
[1]: https://i.stack.imgur.com/uK9Cy.png
inside your fixtures/data/book.php
<?php
return [
'book1' => [
'title' => 'lmayert',
'isbn' => 'Ibn-098',
],
'user2' => [
'title' => 'napoleon69',
'isbn' => 'Ibn-042',
],
];
your fixtures/BookFixture be like this:
<?php
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
/**
*
*/
class BookFixture extends ActiveFixture
{
public $modelClass = 'app\models\Book';
}
Now the tests/unit/BookTest be like this
<?php
use app\tests\unit\fixtures\BookFixture;
class BookTest extends \Codeception\Test\Unit
{
/**
* #var \UnitTester
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
public function _fixtures() {
return [
'books' => 'app\tests\fixtures\BookFixture',
];
}
// tests
public function testBook()
{
$book1 = $this->tester->grabFixture('books','book1');
$this->assertEquals(1,$book1->id);
}
}
I hope this will help

You have to change fixture file name from Person.php to PersonFixture.php and it will start working.

You have to be using yii2-codeception extension which would autoload fixtures for you.
After installing it you would have class yii\codeception\DbTestCase available, PersonTest should extend it.
Person fixture should have namespace as follows: app\tests\fixtures.

Related

Testing forms (form types) in Symfony 3.3 projects

In a Symfony 3.3 project I try to test this simple form:
class FooFormType extends AbstractType
{
private $fooService;
public function __construct(FooService $fooService)
{
$this->fooService = $fooService;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'bar',
EntityType::class,
[
'class' => Bar::class,
'choice_label' => 'title',
'placeholder' => 'Please select a bar',
]
)
->add(
'baz',
ChoiceType::class,
[
'choices' => $this->fooService->lorem(),
]
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Foo::class,
]
);
}
}
I followed the instruction I found at http://symfony.com/doc/current/form/unit_testing.html#testings-types-from-the-service-container to write this (simplified) test:
class FooFormTypeTest extends TypeTestCase
{
protected function getExtensions()
{
$fooServiceDummy = $this->createMock(FooService::class);
$fooFormType = new FooFormType($fooServiceDummy);
$managerRegistryDummy = $this->createMock(ManagerRegistry::class);
$entityFormType = new EntityType($managerRegistryDummy);
return [
new PreloadedExtension([$fooFormType, $entityFormType], []),
];
}
/**
* #test
*/
public function submitValidData()
{
$form = $this->factory->create(FooFormType::class);
}
}
Unfortunatelly this exception is thrown:
Symfony\Component\Form\Exception\RuntimeException: Class "AppBundle\Entity\Bar" seems not to be a managed Doctrine entity. Did you forget to map it?
What is the problem here?

Cakephp 3 use mock to test controller

I want to test a controller using a mock.
In my controller
public function myAction() {
$email = new MandrillApi(['template_name'=>'myTemplate']);
$result = $email
->subject('My title')
->from('no-reply#test.com')
->to('dest#test.com')
->send();
if ( isset($result[0]['status']) && $result[0]['status'] === 'sent' )
return $this->redirect(['action' => 'confirmForgotPassword']);
$this->Flash->error(__("Error"));
}
In test
public function testMyAction() {
$this->get("users/my-action");
$this->assertRedirect(['controller' => 'Users', 'action' => 'confirmForgotPassword']);
}
How do I mock the class MandrillApi ? thank you
In your controller-test:
public function controllerSpy($event){
parent::controllerSpy($event);
if (isset($this->_controller)) {
$MandrillApi = $this->getMock('App\Pathtotheclass\MandrillApi', array('subject', 'from', 'to', 'send'));
$this->_controller->MandrillApi = $MandrillApi;
$result = [
0 => [
'status' => 'sent'
]
];
$this->_controller->MandrillApi
->method('send')
->will($this->returnValue($result));
}
}
The controllerSpy method will insert the mocked object once the controller is setup correctly. You don't have to call the controllerSpy method, it gets executed automatically at some point after you make the $this->get(... call in your test.
Obviously you have to change the App\Pathtotheclass-part of the mock-generation to fit the location of your MandrillApi-class.

DeepCopy::recursiveCopy exception when i run codeception with yii2 framework

When run codeception i got a lots fo errors: DeepCopy\DeepCopy::recursiveCopy.
This is a problem so confuse me!
I use codeception play well when i have only 4 unit test case:
When i still full up the unit test case story happen..
There is 5 unit test in my test class then i got this:
Here is the code :
public function testGetComplexItemNeeds() {
$this->specify('[getComplexItemNeeds] : ', function($expected) {
$actual = \common\services\ConfigService::getComplexItemNeeds('300001');
expect('getComplexItemNeeds', $actual)->equals($expected);
}, ['examples' => [
[0 => [
'gold' => 1,
'list' => [
300018 => 1,
],
]
],
]]);
}
php_error.log:
PHP 195. DeepCopy\DeepCopy::copyArray($array = array ('0000000025e4802e0000000050ab4f11' => class tests\codeception\frontend\UnitTester { protected $scenario = class Codeception\Scenario { ... }; protected $friends = array (...) }, '0000000025e480490000000050ab4f11' => class Codeception\Scenario { protected $test = class tests\codeception\frontend\service\ConfigServiceTest { ... }; protected $steps = array (...);
xdebug:
i use xdebug to trace the problem found that when the function specifyCloneProperties deep copy the unitTester the php script down.
You can turn off deep clone in your bootstrap.php file:
<?php
// disable deep cloning of properties inside specify block
\Codeception\Specify\Config::setDeepClone(false);
?>
Or turn on/off in the test. It's also increased performance of your tests.

Mockery mock not returning specified value

I'm using Mockery in my Laravel project to mock the User Eloquent model and test a route.
This is how I test the /api/user/activate route:
<?php
use Illuminate\Support\Facades\Session;
class ActivateTest extends TestCase
{
private $userMock;
public function setUp()
{
parent::setUp();
$this->userMock = Mockery::mock('App\User');
Session::start();
}
public function tearDown()
{
Mockery::close();
}
public function testActivate()
{
$this->userMock->shouldReceive('where->first')->once()->andReturn('test');
$this->userMock->shouldReceive('activate')->once();
$response = $this->call('POST', '/api/user/activate', [
'activationToken' => '838jfjnvu83u3',
'_token' => csrf_token()
]);
// This will be displayed in the PHPunit output
print_r($response->getContent());
$this->assertResponseStatus(200);
}
}
The problem I'm having is that the andReturn('test') doesn't seem to work. The PHPunit result is:
F{"error":{"message":null,"statusCode":404}}
Time: 276 ms, Memory: 15.50Mb
There was 1 failure:
1) ActivateTest::testActivate
Failed asserting that 404 matches expected 200.
This is the content of the activate() in the UserController:
public function activate(Request $request)
{
$activation = $request->input();
$user = $this->user->where('activationToken', $activation['activationToken'])->first();
if(!$user) return $this->respondNotFound($user);
try
{
$user->activate($activation['password']);
}
catch(ModelException $e)
{
return $this->respondInternalError($e->errorMessages());
};
return $this->respondCreated('Account activated.');
}
The problem is that $user in the controller is null because the mock is not returning test (in that case the condition would evaluate to true and I wouldn't get a 404 response).
Edit:
I also tried using PHPunit mocking but it wasn't successful:
$this->userMock = $this->getMockBuilder('App\User')->setMethods(['where', 'first', 'activate'])->getMock();
$this->userMock->expects($this->once())->method('where')->willReturn($this->userMock);
$this->userMock->expects($this->once())->method('first')->willReturn('test');
$this->userMock->expects($this->once())->method('activate');
It's not enough to mock an object. You need to get that mocked object to be injected into the class which contains that activate() function.
You can do that in your setUp() function as well. Try adding this...
$this->app->instance('App/User', $this->userMock);
That will tell Laravel when you want to inject an instance of App/User, to inject the mock object you just created instead.
The issue was caused by ->first() since it's not a method existing neither on the Eloquent or User classes.
To solve it I created a new UserRepository and injected it as a dependency in the controller constructor.
class UserRepository implements UserRepositoryInterface
{
/**
* #var User
*/
protected $user;
/**
* #param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* #param $activationToken
* #return mixed
*/
public function whereActivationToken($activationToken)
{
return $this->user->where('activationToken', $activationToken)->first();
}
}
Injection in the UserController:
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
And this is how the test PostActivateTest class looks like now:
use Illuminate\Support\Facades\Session;
class PostActivateTest extends TestCase
{
private $user;
private $userRepositoryMock;
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('App\User');
$this->userRepositoryMock = Mockery::mock('Repository\Database\UserRepository');
$this->app->instance('App\User', $this->user);
$this->app->instance('Bloom\BloomCRM\Repository\Database\UserRepository', $this->userRepositoryMock);
Session::start();
}
public function tearDown()
{
Mockery::close();
}
public function testActivate()
{
$this->userRepositoryMock->shouldReceive('whereActivationToken')->once()->andReturn($this->user);
$this->user->shouldReceive('activate')->once();
$this->call('POST', '/api/user/activate', [
'activationToken' => '838jfjnvu83u3',
'password' => 'test',
'_token' => csrf_token()
]);
$this->assertResponseStatus(201);
}
}

how to mock laravel eloquent model

I keep fumbling over this - how do I mock a model that extends form Eloquent in Laravel 4 for my unit test?
I keep getting the following error w/ my current way
ErrorException: Trying to get property of non-object
Example
use \Repository\Text\EloquentText;
use \Faker\Factory as Faker;
class EloquentTextTest extends TestCase {
public function setUp()
{
parent::setUp();
$stub = $this->getMock('Text');
$stub->expects($this->any())->method('save');
$this->_fixture = new EloquentText($stub);
}
/**
* #test
*/
public function createShouldCreateNewTextEntry()
{
$faker = Faker::Create();
$data = [
'title' => $faker->sentence,
'content' => $faker->text,
'level_id' => $faker->randomDigit,
'is_public' => $faker->numberBetween(0, 1),
'is_visible' => $faker->numberBetween(0, 1),
];
$text = $this->_fixture->create($data);
$this->assertEquals($data['title'], $text->title);
$this->assertEquals($data['content'], $text->content);
$this->assertEquals($data['level_id'], $text->level_id);
$this->assertEquals($data['is_public'], $text->is_public);
$this->assertEquals($data['is_visible'], $text->is_visible);
return $text;
}