How can I test a Yii2 model in a library project? - unit-testing

I'm trying to implement an adapter that is using a Yii model object extending yii\db\ActiveRecord. The object is passed as constructor arg to the adapter class.
My issue is now that I still couldn't figure out how to get this to work properly. I've even tried mocking it but got stuck because Yii is using lots of static methods to get it's objects. Sure, I could now try to mock them... But there must be a better way?
public function testSuccessFullFind(): void
{
$connection = (new Connection([
'dsn' => 'sqlite:test'
]))
->open();
$queryBuilder = new \yii\db\sqlite\QueryBuilder($connection);
$app = $this->createMock(Application::class);
\Yii::$app = $app;
$app->expects($this->any())
->method('getDb')
->willReturn($this->returnValue($connection));
$userModel = new UserModel();
$resovler = new Yii2Resolver($userModel);
$result = $resolver->find(['username' => 'test', 'password' => 'test']);
// TBD asserts for the result
}
The UserModel is used to find a user record internally.
This results in:
1) Authentication\Test\Identifier\Resolver\Yii2ResolverTest::testSuccessFullFind
Error: Call to a member function getDb() on null
vendor\yiisoft\yii2-dev\framework\db\ActiveRecord.php:135
vendor\yiisoft\yii2-dev\framework\db\ActiveQuery.php:312
vendor\yiisoft\yii2-dev\framework\db\Query.php:237
vendor\yiisoft\yii2-dev\framework\db\ActiveQuery.php:133
tests\TestCase\Identifier\Resolver\Yii2ResolverTest.php:31
The code above is obviously the WIP of a test case.
So how can I configure a test connection and get my ActiveRecord object to use it?

You can pass connection as argument of all() method:
$results = UserModel::find()->where(['id' => 1])->all($connection);

Related

How can be any service injected into WebTestCase subclass in Symfony?

Maybe I am missing something... doh, I think so, but could not find an answer to that.
WebTestCase generates this constructor sample:
public function __construct(?string $name = null, array $data = [], string $dataName = '')
{
parent::__construct($name, $data, $dataName);
}
Was trying to add my service as the first or last argument - Symfony throws an error:
Type error: Too few arguments to function Tests\AppBundle\Manager\ContactManagerTest::__construct(), 0 passed in /Library/WebServer/Documents/HEPT/vendor/bin/.phpunit/phpunit-5.7/src/Framework/TestSuite.php on line 568 and at least 1 expected in /Library/WebServer/Documents/HEPT/tests/AppBundle/Manager/ContactManagerTest.php:22
Should I somehow use container directly? Why is autowiring not working for WebTestCase classes if there is a bridge class?
WebTestCase are used in the context of PHPUnit (which has nothing to do with Symfony and its dependency injection).
They actually generate the kernel and its container, see this piece of code extracted from Symfony source code:
protected static function createClient(array $options = array(), array $server = array())
{
$kernel = static::bootKernel($options);
$client = $kernel->getContainer()->get('test.client');
$client->setServerParameters($server);
return $client;
}
This means that you can easily access the container like this:
$kernel = static::bootKernel($options);
$container = $kernel->getContainer();
Please note also that static::$kernel->getContainer() is available as soon as you created your client to make your test.

Symfony 3.2 - set environment variables in runtime [duplicate]

In my config.yml I have this:
parameters:
gitek.centro_por_defecto: 1
Now, I want to change this value from my controller using a form, like this:
public function seleccionAction(Request $request)
{
$entity = new Centro();
$form = $this->createForm(new SeleccionType(), $entity);
$centro = $this->container->getParameter('gitek.centro_por_defecto');
if ($this->getRequest()->getMethod() == 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$miseleccion = $request->request->get('selecciontype');
$this->container->setParameter('gitek.centro_por_defecto', $miseleccion['nombre']);
// return $this->redirect($this->generateUrl('admin_centro'));
}
}
return $this->render('BackendBundle:Centro:seleccion.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
I´m getting Impossible to call set() on a frozen ParameterBag. error all the time.
Any help or clue?
You can't modify Container once it has been compiled, which is done before invoking the controller.
The DIC parameters are intended for configuration purposes - not a replacement for global variables. In addition it seems you want to persist some kind of permanent modification. In that case consider using session if it's a per-user modification or persisting it (e.g. into DB) if it's supposed to be application-wide.
If you need to modify DIC parameters or services, you can do so using a compiler pass. More info on how to write custom compiler passes can be found at:
http://symfony.com/doc/master/cookbook/service_container/compiler_passes.html
You can set $_ENV variables and get that after
putenv("VAR=1");
And to get
getenv("VAR");

Test JSON-returning controller method without MissingViewError

I am testing a Controller method that has only a JSON view. My method runs as expected, but the test method only returns "MissingViewException". Is there a solution to avoiding this exception in the unit test (besides inserting an empty file at View/People/map_leads.ctp)?
PeopleController.php
public function mapLeads($territory_id = null) {
$leads = $this->Person->getPeople([
'territory_id' => $territory_id
]);
$this->set('leads', $leads);
}
AppController.php
public $components = ['RequestHandler'];
routes.php
Router::parseExtensions('json');
PeopleControllerTest.php
public function testMapLeads() {
$id = 40;
$result = $this->testAction('/people/mapLeads/' . $id, array('return' => 'vars'));
}
View/People/json/map_leads.ctp exists and is properly utilized by CakePHP; it is only the test that wants to see View/People/map_leads.ctp.
I checked at CakePHP: calling testAction to a json-returning method causes missing view exception reminding about adding RequestHandler to $components. This does not resolve the exception.
You aren't issuing a JSON request/accessing a JSON endpoint, as neither your request URL does contain the .json extension, nor does your request send an appropriate Accept header (I don't remember whether the latter is possible with the 2.x controller test case class at all).
Use the .json extension and you should be good.
$this->testAction('/people/mapLeads/' . $id . '.json', array('return' => 'vars'));
Write this code inside your action.
$this->autoLayout = false;
$this->autoRender = false;
$this->response->type('application/javascript');

Mocking an Eloquent collection response from another mocked class

I Have looked at many questions along the same line of thought here on stack overflow, and else where but unable to find a solution to this particular issue.
I'm fairly new to Unit Testing in general, so the mistake may be (hopefully) obvious to someone with more experience.
Here's the issue:
I have a ResourceController that injects a class into the constructor using Depedency Injection.
public function __construct(ResourceAPIInterface $api)
{
$this->api = $api;
}
When that API is called in the controller, the class that was injected does some business logic and returns an Eloquent Collection.
public function index($resource, $version)
{
$input = Input::all();
//Populate Data
$data = $this->api->fetchAll($input);
//Format response
if($data->isEmpty()){
//Format response
$response = Response::make(" ", 204);
}else {
//Format response
$response = Response::make($data, 200);
}
//Set content-type in header
$response->header('Content-Type', 'application/json');
$response->header('Cache-Control', 'max-age=3600');
return $response;
}
As you can see from the code above, I need the response to be an eloquent response so i can test to see if it's empty. The method FetchAll literally just returns a Eloquent collation of all records in the table. When I do the test, i'm able to mock the API without issue. However when i'm mocking the response, i really want the response to be an eloquent collection, and having difficulty getting that to work. Here's an example of the test:
$course = Mockery::mock(new API\Entity\v1\Test);
$this->mock->shouldReceive('fetchAll')->once()->andReturn($course->all());
$this->mock->shouldReceive('name')->once()->andReturn('Course');
// Act...
$response = $this->action('GET', 'ResourceController#show');
// Assert...
$this->assertResponseOk();
The above works, but when i want to do the same test against the show method and mock the eloquent response for ->first() I'm getting errors.
1) ResourceControllerTest::testshow
BadMethodCallException: Method Mockery_1_API_Entity_v1_Test_API_Entity_v1_Test::first() does not exist on this mock object
I've tried to test the model by doing:
$course = Mockery::mock('Eloquent', 'API\Entity\v1\Test');
$response = $course->mock->shouldReceive('find')->with(1)->once()->andReturn((object)array('id'=>1, 'name'=>'Widget-name','description'=>'Widget description'));
However when I run that in the Test I get the following error:
1) ResourceControllerTest::testIndex
BadMethodCallException: Method Mockery_1_API_Entity_v1_Test::getAttribute() does not exist on this mock object
Any Ideas on how to resolve this issue? Also, if there's a better way to test if the eloquent collection is empty that might resolve some of the complexity that I'm running into is also welcome.
Ok, I figured out how to make this work:
public function testIndex($resource="course", $version="v1")
{
// Arrange...
$course = Mockery::mock('Eloquent', 'API\Entity\v1\Page')->makePartial();
$course->shouldReceive('isEmpty')->once()->andReturn(false);
$course->shouldReceive('all')->once()->andReturn($course);
$this->mock->shouldReceive('fetchAll')->once()->andReturn($course->all());
$this->mock->shouldReceive('name')->once()->andReturn('Course');
// Act...
$response = $this->action('GET', 'ResourceController#index');
// Assert...
$this->assertResponseOk();
}
I was able to do the PartialMock to get around the getAttribute() Error. Once I did that, I started getting the error:
Call to undefined method stdClass::isEmpty()
So I decided to mock that as well, and pass the whole mocked object into the expected response for the all command.
Then in the mock for the API class $this->mock-> i had it return The mocked eloquent collection with the ->all() method.
This is also working for the other test i had for find($id). That one however didn't require an isEmpty() check so was easier to mock.

Issue with $softDelete in model testing

I am using laravel-test-helper package by jeffrey way in laravel 4 to test my models. When I use
protected $softDelete = true;
in my model, the following assertion fails:
public function testMyModel()
{
$obj = Factory::create('Modelname');
Assert::equals(1,$obj->count());
}
and when I run the test without the $softDelete variable, it works fine.
Any clue to whats wrong?
This is the stack trace of the phpunit command, if it is of any help:
/var/www/project/vendor/way/laravel-test-helpers/src/Way/Tests/TestFacade.php:41
/var/www/project/vendor/way/laravel-test-helpers/src/Way/Tests/TestFacade.php:25
/var/www/project/vendor/way/laravel-test-helpers/src/Way/Tests/TestFacade.php:55
/var/www/project/app/tests/models/ModelTest.php:13
/var/www/project/app/tests/models/ModelTest.php:13
The Factory::create() will create random variables for your models. Because your are using 'softDelete' - there is a field in your table called 'deleted_at'. So the factory is putting data in there, and Laravel thinks the record is deleted, so ->count() returns 0.
You need to explicitly set Factory default for the deleted_at to null like this:
$obj = Factory::create('Modelname', array('deleted_at' => null));