Im writing some unit tests and bear with me I am still very new to unit testing.
The issue I am having is a lot of my saves invoke a behaviour that requires
the users id from Yii::app()->user->id.
However when I run the UnitTest I get problems as the user isn't logged in.
Is there anyway I can either ignore the behaviour by a flag (e.g. if ($isInTestingMode)) or log the user in within the testing class?
I would probably build a user object that you use in your tests. And then in the appropriate tests (as part of the setup method, like ernie describes in his comment), swap in the testing user object.
The test user object would then have a method that works like this:
public function getId() {
return 12;
}
public function getIsGuest() {
return false;
}
The above is what they call a 'Fake' object.
In your setup method you'd use the following lines:
Yii::app()->configure(array(
'components' => array(
'user' => array(
'class' => 'path.to.FakeUser',
)
)
));
You can also add that to your test config file if you want that to be the default user (and then swap in the normal CWebUser/WebUser model in tests that need to have a non-logged in user.
Or you could have a flag you set for your FakeUser (isLoggedIn = true/false) in each unit test. I'd probably go with this option myself ...
Tests like these are not unit tests. Unit tests are for testing individual functions in a class. A "logged in user" covers a large swath of code (logging in, session management, navigating to a specific page, etc.). I believe you're looking for functional testing. You can learn more about that here: http://www.yiiframework.com/doc/guide/1.1/en/test.functional
Related
I have this test:
public function test_user_can_access_the_application_page(
{
$user=[
'email'=>'user#user.com',
'password'=>'user1234',
];
$response=$this->call('POST','/login',$user);
$this->assertAuthenticated();
$response->assertStatus(302)
->assertRedirect('/dashboard')
->assertLocation('/dashboard');
$response=$this->call('GET','/application/index');
$response->assertLocation('/application/index');
}
After I log in, it directs me to the dashboard ok until now, but if I want to access the other page after that, I cant. This error comes up.
Expected :'http://mock.test/application/index'
Actual :'http://mock.test'
Aren't multiple calls allowed in the same test, or is another way to access other pages after login?
(Note: It's not possible to use factories for the actingAs so I need to login).
If you can't use factories for actingAs, then you should try with cookie.
Look at the https://github.com/firebase/php-jwt library.
I guess you will need to call the function as an user, since you can only access it logged in. Laravel provides the actingAs() method for such cases.
https://laravel.com/docs/7.x/http-tests#session-and-authentication
You can create a random User who has the permission to log into your app or take a seeded one and call the function acting as the chosen User.
$response=$this->actingAs($user)->call('GET','/application/index');
If you call it without actingAs(), your middleware will redirect you back to the login or home screen (what you defined in the LoginController ).
In my opinion this test case should have its own testing method. I recommend using a test method per route or per use case. It makes your tests clearly arranged and easy to understand.
If you want to be authenticated, the easiest way is to have PHPUnit simulate authentication using the actingAs() method.
This method makes the user authenticated, so you wouldn't want to test the login method with it. You should write your login tests separate from testing the other pages.
To answer your question, yes you can make multiple requests in the same test, but in this case linking the login test to the 'application/index' page likely does not make much sense.
public function test_the_user_can_login()
{
$user = [
'email'=>'user#user.com',
'password'=>'user1234',
];
$response = $this->call('POST','/login',$user);
$this->assertAuthenticated();
$response->assertStatus(302)
->assertRedirect('/dashboard')
->assertLocation('/dashboard');
}
public function test_user_can_access_the_application_page()
{
$user = User::where($email, "user#user.com")->first();
$response = $this->actingAs($user)
->call('GET','/application/index');
$response->assertLocation('/application/index');
}
I experienced that in laravel 8 I use comment #test and involved second test!!! I mean if you use two function for test you must us #test that php artisan test, test both of them.
public function addPic($loggedInId,$PicId){
$chatCoverPhotoObj = new COVER_PHOTO();
$out = $chatCoverPhotoObj->add($loggedInId,$PicId);
if($out)
return true;
return false;
}
Above sample code is written in php and simple add record in table COVER_PHOTO
Record : "picId" corresponding to loggedin user identified by "loggedInId".
I want know what should i write in unit test of this function.
Or for such function we should not write unit test.
Please suggest?
Firsty, if your System-/Class Under Test is using anything that is infrastructural concern -which in your case is the database- that tends to be an integration test.
Unit test are happening in complete isolation with every possible dependencies mocked/stubbed out.
The first problem I see in your code is that you new() up a dependency of your function. By this you can't really easily test your code.
I suggest that you better invert this dependency and have an interface in the constructor of your class, which can receive the concrete implementation of that interface. Once this is done, you can mock out the database part in your test. However by looking at the code you have shared in your question I'm not really convinced what behaviour you are going to test here.
Always make sure what you want to test for, in this case like persistence specification or the mapping configuration (of course in case you are using some sort of an OR/M). Just checking the return value -which is the success or failure flag- of the persistence operation doesn't add much business value to test against.
When I use the Session facade within a Laravel Test, I get failures within HHVM systems. I'm using Travis.CI to test a range of systems from PHP 5.4 right up to PHP 7, including HHVM. Throughout my range of tests, wherever Sessions are used, the tests fail, but only on HHVM systems; all others pass.
Here's an example test:
/**
* Tests that, provided all the data lines up, we can add points to a child
*/
public function testPointAdditionWithCorrectChild()
{
Session::start();
// Set up User
$user = factory(User::class)->create();
$this->be($user);
// Create Child
$child = factory(Child::class)->create([
'user_id' => $user->id
]);
// Set up Request
$pointData = [
'_token' => csrf_token(),
'child' => $child->id,
'points' => mt_rand(1, 10)
];
$this->post('/point/increment', $pointData);
// Assertion
$this->seeInDatabase('points', [
'child_id' => $pointData['child'],
'point_difference' => $pointData['points']
]);
}
The purpose of the above test is to ensure that my middleware functions when authenticating a child against a user with the correct credentials - restricting the ability to add/remove points so that only the user who created the child is allowed to do this.
Laravel comes with a $this->withoutMiddleware(); flag which allows me to turn off middleware for a particular test. However, as I'm testing my middleware in this instane, I cannot use it, meaning I also have to satisfy the all the other middlewares assigned to the controller as well, such as the VerifyCsrfToken middleware. To do this, I have to run Session::start() otherwise I cannot take advantage of the csrf_token() function. Therein lies my problem. I suppose it's an integration test, more than a unit test.
Is there something specific about the way that HHVM deals with sessions that is causing these tests to fail?
I have a class let's say Person. The ORM layer has generated based on the sql structure the corresponding objects. The class person has a method: Get($id). In the Get method, the object Person is called and the field from the table are retrieved.
I basically want to make the following unit test: create a new person and check if the Get method is returning the right information.
How is the unit testing supposed to work in this condition ?
Do I need to create a separate database ( just the structure ), and make the creation/selection from that database?
Should the boostrap file load the same configuration as the framework I'm using but change the configuration file so It works with the fake database ?
Should I clean the new database each after each test ?
I was also wandering after seeing your responses if simulating an ORM response without actually building a new database is not the way to go ?
How is the unit testing supposed to work in this condition ?
Generally, you should split your unittests mentally in two parts:
one part of your code is testable without the database, so you can stub or mock the methods that do the database access
the other part of your tests needs to work with the database, since it tests if the ORM is used correctly.
Do I need to create a separate database
This depends on your needs. Rails apps generally have testing/development/production "environments" - database, configuration, storage directories.
Testing is for running unit tests, dev for developing things and production for running the live server. While developing, you run against the dev configuration and thus your development database. For unit tests, the testing env is used which has the benefit that i.e. users in the database are not deleted or broken.
I like that concept; in my phpunit tests I often do have a switch in the bootstrap that changes which configuration file is loaded. Just remember that your development database often contains more data than a single unit test needs, and you probably hand-crafted that data and do not want to lose. Also, another database does not cost money.
Should I clean the new database each after each test?
I mostly clean the tables only that will be used in the test. Cleaning your database makes sure you don't get side-effects from previous tests.
Check out Phactory. I prefer it over the database extensions included in PHPUnit and it makes it really easy to insert records into your test db.
require_once 'Phactory/lib/Phactory.php';
Phactory::setConnection(new PDO('sqlite:test.db'));
Phactory::define('user', array('name' => 'Test User',
'email' => 'user#example.com'));
$user = Phactory::create('user'); // creates a row in the 'users' table
print("Hello, {$user->name}!"); // prints "Hello, Test User!"
Your System Under Test (SUT) will need to connect to your test database. The idea is that you populate just the records you need for the method you are testing. The orm layer shouldn't matter if the test db has all the same tables and fields as your production database.
PHPUnit also provides some help with this, have a look at Database Testing.
Essentially you can write you Test classes so that they extend PHPUnit_Extensions_Database_TestCase and then use the getConnection() and getDataSet() functions to load up data for the test.
require_once 'PHPUnit/Extensions/Database/TestCase.php';
class PersonTest extends PHPUnit_Extensions_Database_TestCase
{
protected function getConnection() {
$pdo = new PDO('mysql:host=localhost;dbname=application_test', 'root', '');
return $this->createDefaultDBConnection($pdo, 'application_test');
}
protected function getDataSet() {
return $this->createMySQLXMLDataSet('person.xml');
}
Then you can define exactly what you want to test in the database in the XML.
You can also assert that the resulting DataSet from your tests is equal to what you expect with:
public function testCreate() {
// Execute some code with your ORM to create a person.
$actual = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection());
$actual->addTable('person');
$expected = $this->createMySQLXMLDataSet('person_create_expected.xml');
$this->assertDataSetsEqual($expected, $actual);
}
In this example, we are only comparing the resulting person table... So person_create_expected.xml should only contain the person table as well.
To create the XML's you can use mysqldump.
mysqldump --xml -t -u root -p application_test > person.xml
I am trying to focus a bit on unit testing using PHPunit.
I have found a very good tutorial over here http://blog.nickbelhomme.com/php/phpunit-training-course-for-free_282
But there is something I'm missing and don't yet understand how to do.
I have a user module which maintains all information about users. And there is a function save which saves the user in the database. So I have a testFunction
public function testCanCreateUser()
{
$userData = array(
'userName' => 'User1',
'firstName' => 'Joey',
'lastName' => 'Hendricks',
'email' => 'Joey#hendricks.com',
'password' => 'f$tfe8F'
);
$user = new Model_User($userData);
$user->save();
}
The first time when I will run my test this will work. Since the database is empty. But When I run my tests for the second time it won't work since my system doesn't allow the same user twice in the db. So In order to do this I have to recreate my testdatabase every time before I run my tests. What is the best way to do this?
Or is this problem to be solved on a different way?
If you want to test your business logic: Mock away the Database class and return fake data
If you want to test the class that fires the SQL statements (and imho you could test that too since I kinda wanna know if my code works fine with a real db in the backend) it gets a little complicated but there are ways to do it:
Using setUp() and tearDown() to get a consistent state for your data before running your tests is (imho) a fine way to write db-driven unittests. It can get annoying to write lots of custom sql by hand though.
To make your life a little easier you can look into the DbUnit extension and see if that works for your Application.
If you really want to dive into Unittesting database interactions the best read on the subject is (imho) the chapter on db-unittesting in Sebastian Bergmanns phpqa book.
Could your application allow for a custom database name and automated setup of all tables it may also be possible to set the db up once with a lot of testdata and use that data in all your tests. You could be carefull so though that one test doesn't rely on data written by another one.
Run tests with other copy of the database that is empty and/or cleared in setUp() or tearDown() methods, but be careful not to do what github did.
If you're using a good database (i.e. not MySQL with MyISAM tables) you can wrap test in a transaction and roll it back after the test:
function setUp() { $this->db->exec("BEGIN"); }
function tearDown() { $this->db->exec("ROLLBACK"); }
The downside is that you can't test code that uses transactions (unless you abstract that and emulate with savepoints, but that's iffy).
Ideally you should use dependency injection and run tests on fake database class:
$fakedb = new DatabaseThatDoesntReallySaveThings();
$user = new Model_User($fakedb, $userData);
$user->save();
$this->assertTrue($fakedb->wasAskedToSaveUser());
I think you can use tearDown() method to clean your saved data.
protected $_user;
public function testCanCreateUser()
{
...
$this->_user = new Model_User($userData);
$this->_user->save();
}
public function tearDown()
{
$this->_user->delete();
}