Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
I'm using Symfony 2.8 (latest) for an web-application where every part of the application which could be used alone / reused is an own bundle. For example there is a NewsBundle, GalleryBundle, ContactBundle, AdminBundle (this is a special case - it's only a wrapper-bundle for EasyAdminBundle collecting the traits provided by the specific bundles), UserBundle (Child bundle of FOSUserBundle storing user-entity and templates)
My question is basically, whats the best structure for unit-tests?
Let me explain it a little bit more: In my UserBundle I want to make tests for my implementation of FOSUserBundle. I have a method testing the login-page (via HTTP status-code), login-failure (via error-message), login-successful (via specific code-elements), remember-me (via Cookie), logout (via page-content)
<?php
namespace myNamespace\Admin\UserBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* Class FOSUserBundleIntegrationTest.
*/
class FOSUserBundleIntegrationTest extends WebTestCase
{
/**
* Tests the login, login "remember-me" and logout-functionality.
*/
public function testLoginLogout()
{
// Get client && enable to follow redirects
$client = self::createClient();
$client->followRedirects();
// Request login-page
$crawler = $client->request('GET', '/admin/login');
// Check http status-code, form && input-items
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertEquals(1, $crawler->filter('form[action="/admin/login_check"]')->count());
$this->assertEquals(1, $crawler->filter('input[name="_username"]')->count());
$this->assertEquals(1, $crawler->filter('input[name="_password"]')->count());
$this->assertEquals(1, $crawler->filter('input[type="submit"]')->count());
// Clone client and crawler to have the old one as template
$clientLogin = clone $client;
$crawlerLogin = clone $crawler;
// Get form
$formLogin = $crawlerLogin->selectButton('_submit')->form();
// Set wrong user-data
$formLogin['_username'] = 'test';
$formLogin['_password'] = '123';
// Submit form
$crawlerLoginFailure = $clientLogin->submit($formLogin);
// Check for error-div
$this->assertEquals(1, $crawlerLoginFailure->filter('div[class="alert alert-error"]')->count());
// Set correct user-data
$formLogin['_username'] = 'mmustermann';
$formLogin['_password'] = 'test';
// Submit form
$crawlerLoginSuccess = $client->submit($formLogin);
// Check for specific
$this->assertTrue(strpos($crawlerLoginSuccess->filter('body')->attr('class'), 'easyadmin') !== false ? true : false);
$this->assertEquals(1, $crawlerLoginSuccess->filter('li[class="user user-menu"]:contains("Max Mustermann")')->count());
$this->assertEquals(1, $crawlerLoginSuccess->filter('aside[class="main-sidebar"]')->count());
$this->assertEquals(1, $crawlerLoginSuccess->filter('div[class="content-wrapper"]')->count());
// Clone client from template
$clientRememberMe = clone $client;
$crawlerRememberMe = clone $crawler;
// Get form
$formRememberMe = $crawlerRememberMe->selectButton('_submit')->form();
// Set wrong user-data
$formRememberMe['_username'] = 'mmustermann';
$formRememberMe['_password'] = 'test';
$formRememberMe['_remember_me'] = 'on';
// Submit form
$crawlerRememberMe = $clientRememberMe->submit($formRememberMe);
// Check for cookie
$this->assertTrue($clientRememberMe->getCookieJar()->get('REMEMBERME') != null ? true : false);
// Loop all links on page
foreach ($crawlerRememberMe->filter('a')->links() as $link) {
// Check for logout in uri
if (strrpos($link->getUri(), 'logout') !== false) {
// Set logout-link
$logoutLink = $link;
// Leave loop
break;
}
}
// Reuse client to test logout-link
$logoutCrawler = $clientRememberMe->click($logoutLink);
// Get new client && crawl default-page
$defaultPageClient = self::createClient();
$defaultPageCrawler = $defaultPageClient->request('GET', '/');
// Check http status-code, compare body-content
$this->assertTrue($defaultPageClient->getResponse()->isSuccessful());
$this->assertTrue($logoutCrawler->filter('body')->text() == $defaultPageCrawler->filter('body')->text());
}
}
All this tests will be done in one method because if I would do it in different methods I would have an high amount (5x4 lines = 20 lines copy & paste) of duplicated code. Does this follow the best practice? What is the best practice for separating unit-tests? (or other worded: How would you do it?)
Part two of the question: Is there a possibility to provide helper-functions for test-classes or something working similar to it? I mean methods as example which provide an logged in client. This would be needed for admin function-tests.
Now that your question is more specific, I will provide an answer with some explanation. What you are doing for your first test may work but is not the way you should be testing. It's not so much best practice as it is you circumventing the idea of a unit test, checking assumptions against a single unit of work. Your test has several 'units' of work being tested, and they should all be in separate tests.
Here is a condensed example of more appropriate tests for your first two cases:
public function testLoginForm()
{
$client = self::createClient();
$crawler = $client->request('GET', '/admin/login');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertEquals(1, $crawler->filter('form[action="/admin/login_check"]')->count());
$this->assertEquals(1, $crawler->filter('input[name="_username"]')->count());
$this->assertEquals(1, $crawler->filter('input[name="_password"]')->count());
$this->assertEquals(1, $crawler->filter('input[type="submit"]')->count());
}
public function testLoginFailure()
{
$client = self::createClient();
$crawler = $client->request('GET', '/admin/login');
$form = $crawler->selectButton('_submit')->form();
$form['_username'] = 'test';
$form['_password'] = '123';
$crawler = $client->submit($form);
$this->assertEquals(1, $crawler->filter('div[class="alert alert-error"]')->count());
}
A few things here.
You are worried about code duplication and extra lines of code, but I just created two separate tests that didn't increase the line count at all. I was able to remove the followRedirects() call since it didn't apply to those tests, and I eliminated the two lines of cloning by simply recreating the client and crawler as normal, which is less confusing.
With your code there is only one unit test, but if that test fails it could be for any number of different reasons - login failure, login success, etc. So if that test fails you have to sift through the error messages and find out which part of your system failed. By separating out the tests, when a test fails you know what went wrong simply by the name of the test.
You can eliminate some of your redundant code comments by separating your tests: // Set wrong user-data is no longer needed because the test itself is called testLoginFailure().
Not only is it unit-testing best practice, but there is another caveat when it comes to using WebTestCase, in that you want all of your tests isolated. I've tried to make a static $client variable that the entire class can use, thinking that I'll save memory/time if I only instantiate one instance, but this causes unpredictable behavior when you start running multiple tests. You want your tests to occur in isolation.
You could also use the setUp() and tearDown() functions and have a $this->client and $this->crawler instantiated before each request if you were really trying to eliminate redundant code:
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Component\DomCrawler\Crawler;
/*
* #var Client
*/
private $client;
/*
* #var Crawler
*/
private $crawler;
/*
* {#inheritDoc}
*/
protected function setUp()
{
$this->client = self::createClient();
$this->crawler = $this->client->request('GET', '/admin/login');
}
/*
* {#inheritDoc}
*/
protected function tearDown()
{
unset($this->client);
unset($this->crawler);
}
...but then you're creating class-level code to declare those variables, instantiate them, and tear them down. You also ended up adding a lot of additional code, which is what you were trying to avoid in the first place. Additionally, your entire test class is now rigid and inflexible, because you can never request a page other than the login page. Plus, PHPUnit itself states:
The garbage collection of test case objects is not predictable.
The above statement is in regard to if you don't remember to manually clean up your tests. So you may encounter unexpected behavior for those reasons in addition to the other ones I described above.
As for your second question, sure, provide helper-functions or extend the existing *TestCase classes. The Symfony documentation even provides an example for this with a private function that logs in a user. You could put that in an individual test class like their documentation, or you could make your own MyBaseTestCase class that has that function in it.
TL;DR Don't try to be clever with your tests/test cases, separate your tests, and create helper functions or base test case classes to extend from if you reuse a lot of the same setups.
Related
So I have this Laravel controller and I want to test it.
It is an OAuth client, so a callback is needed to finish the setup.
I want to test the OAuth callback.
The code is something like this:
public function callback(): \Illuminate\Http\RedirectResponse
{
$aCookie = request()->cookie('cookie');
$authCode = request()->get('code')
$connection = OAuthpackage::getConnection();
// Store the credentials in the database
}
To test if the credentials are stored correctly in de database I want to mock the OAuthpackage because it is fine to give it face credentials.
My first thought was to just test the controller by calling that directly. The thing with that is that I don't only need to mock the Oauthpackage but also the request class because I need to face the cookie getting in the code in the GET request.
Now I read on the internet that you probably would not want to mock the request class.
So I thought about just doing the request in the test and then seeing the output.
1 It is just the regular flow
2 I need only one mock
This is what I came up with:
public function testAppCallback()
{
$user = Auth::user();
$connectionFactory = $this->createMock(OAuthPackage::class);
$connectionFactory->method('getConnection')->willReturn(new DummyConenection());
$this->disableCookieEncryption();
$response = $this->withCookies([
'cookie' => 'http://localhost',
])->get('/oauth?code=my_auth_code');
}
The DummyClass just inherits from the real class, but this way I can test its type in the debugger. Turns out that the DummyClass is not being used.
It seems like Laravel boots up a whole new instance as soon as I make a web request and therefore forgets all about the DummyClass.
How should I go about to solve this problem?
I'm trying to get familiar with unit testing in PHP with a small API in Lumen.
Writing the first few tests was pretty nice with the help of some tutorials but now I encountered a point where I have to mock/ stub a dependency.
My controller depends on a specific custom interface type hinted in the constructor.
Of course, I defined this interface/implementation-binding within a ServiceProvider.
public function __construct(CustomValidatorContract $validator)
{
// App\Contracts\CustomValidatorContract
$this->validator = $validator;
}
public function resize(Request $request)
{
// Illuminate\Contracts\Validation\Validator
$validation = $this->validator->validate($request->all());
if ($validation->fails()) {
$response = array_merge(
$validation
->errors() // Illuminate\Support\MessageBag
->toArray(),
['error' => 'Invalid request data.']
);
// response is global helper
return response()->json($response, 400, ['Content-Type' => 'application/json']);
}
}
As you can see, my CustomValidatorContract has a method validate() which returns an instance of Illuminate\Contracts\Validation\Validator (the validation result). This in turn returns an instance of Illuminate\Support\MessageBag when errors() is called. MessageBag then has a toArray()-method.
Now I want to test the behavior of my controller in case the validation fails.
/** #test */
public function failing_validation_returns_400()
{
$EmptyErrorMessageBag = $this->createMock(MessageBag::class);
$EmptyErrorMessageBag
->expects($this->any())
->method('toArray')
->willReturn(array());
/** #var ValidationResult&\PHPUnit\Framework\MockObject\MockObject $AlwaysFailsTrueValidationResult */
$AlwaysFailsTrueValidationResult = $this->createStub(ValidationResult::class);
$AlwaysFailsTrueValidationResult
->expects($this->atLeastOnce())
->method('fails')
->willReturn(true);
$AlwaysFailsTrueValidationResult
->expects($this->atLeastOnce())
->method('errors')
->willReturn($EmptyErrorMessageBag);
/** #var Validator&\PHPUnit\Framework\MockObject\MockObject $CustomValidatorAlwaysFailsTrue */
$CustomValidatorAlwaysFailsTrue = $this->createStub(Validator::class);
$CustomValidatorAlwaysFailsTrue
->expects($this->once())
->method('validate')
->willReturn($AlwaysFailsTrueValidationResult);
$controller = new ImageResizeController($CustomValidatorAlwaysFailsTrue);
$response = $controller->resize(new Request);
$this->assertEquals(400, $response->status());
$this->assertEquals(
'application/json',
$response->headers->get('Content-Type')
);
$this->assertJson($response->getContent());
$response = json_decode($response->getContent(), true);
$this->assertArrayHasKey('error', $response);
}
This is a test that runs ok - but can someone please tell me if there is a better way to write this? It doesn't feel right.
Is this big stack of moc-objects needed because of the fact that I'm using a framework in the background? Or is there something wrong with my architecture so that this feels so "overengineered"?
Thanks
What you are doing is not unit testing because you are not testing a single unit of your application. This is an integration test, performed with unit testing framework, and this is the reason it looks intuitively wrong.
Unit testing and integration testing happen at different times, at different places and require different approaches and tools - the former tests every single class and function of your code, while latter couldn't care less about those, they just request APIs and validate responses. Also, IT doesn't imply mocking anything because it's goal is to test how well your units integrate with each other.
You'll have hard time supporting tests like that because every time you change CustomValidatorContract you'll have to fix all the tests involving it. This is how UT improves code design by requiring it to be as loosely coupled as possible (so you could pick a single unit and use it without the need to boot entire app), respecting SRP & OCP, etc.
You don't need to test 3rd party code, pick an already tested one instead. You don't need to test side effects either, because environment is just like 3rd party service, it should be tested separately (return response() is a side effect). Also it seriously slows down the testing.
All that leads to the idea that you only want to test your CustomValidatorContract in isolation. You don't even need to mock anything there, just instantiate the validator, give it few sets of input data and check how it goes.
This is a test that runs ok - but can someone please tell me if there is a better way to write this? It doesn't feel right. Is this big stack of moc-objects needed because of the fact that I'm using a framework in the background? Or is there something wrong with my architecture so that this feels so "overengineered"?
The big stack of mock objects indicates that your test subject is tightly coupled to many different things.
If you want to support simpler tests, then you need to make the design simpler.
In other words, instead of Controller.resize being one enormous monolithic thing that knows all of the details about everything, think about a design where resize only knows about the surface of things, and how to delegate work to other (more easily tested) pieces.
This is normal, in the sense that TDD is a lot about choosing designs that support better testing.
I'm (we're) creating a package that acts as a core component for our future CMS and of course that package needs some unit tests.
When the package registeres, the first thing it does is set the back/frontend context like this:
class FoundationServiceProvider extends ServiceProvider
{
// ... stuff ...
public function register()
{
// Switch the context.
// Url's containing '/admin' will get the backend context
// all other urls will get the frontend context.
$this->app['build.context'] = request()->segment(1) === 'admin'
? Context::BACKEND
: Context::FRONTEND;
}
}
So when I visit the /admin url, the app('build.context') variable will be set to backend otherwise it will be set to `frontend.
To test this I've created the following test:
class ServiceProviderTest extends \TestCase
{
public function test_that_we_get_the_backend_context()
{
$this->visit('admin');
$this->assertEquals(Context::BACKEND, app('build.context'));
}
}
When I'm running the code in the browser (navigating to /admin) the context will get picked up and calling app('build.context') will return backend, but when running this test, I always get 'frontend'.
Is there something I did not notice or some incorrect code while using phpunit?
Thanks in advance
Well, this is a tricky situation. As I understand it, laravel initiates two instances of the framework when running tests - one that is running the tests and another that is being manipulated through instructions. You can see it in tests/TestCase.php file.
So in your case you are manipulating one instance, but checking the context of another (the one that did not visit /admin and is just running the tests). I don't know if there's a way to access the manipulated instance directly - there's nothing helpful in documentation on this issue.
One workaround would be to create a route just for testing purposes, something like /admin/test_context, which would output the current context, and the check it with
$this->visit('admin/test_context')->see(Context::BACKEND);
Not too elegant, but that should work. Otherwise, look around in laravel, maybe you will find some undocumented feature.
I am testing the endpoints/API of a web application. I have multiple small tests that depend on the return values of the preceding tests. Some of the tests even depend on side effects that are made by the preceding tests. Here's an example of how it goes (the numbered list items represent individual test cases):
Make a request to an endpoint and assert the http code is 200, return the response
Parse the response body and do some assertions on it, return the parsed response body
Do some assertions on the debug values of the parsed response body
Make a new request to another endpoint and assert the http code is 200, return the response
Parse the response body and assert that the side effects from test 1 actually took place
As you can see the tests sort of propagate from test 1, and all the other tests depend on its return value or side effects.
Now I want to execute these tests with data from a data provider to test the behavior with multiple users from our application. According to the phpunit documentation, this is not possible. From the docs:
When a test depends on a test that uses data providers, the depending test will be executed when the test it depends upon is successful for at least one data set. The result of a test that uses data providers cannot be injected into a depending test.
Just to be clear, what I want is for test 1 to execute x number of times with y values, and have all the other tests propagate its return value or check its side effects each time.
After some googling, the only solution that comes to mind is to put all the tests into one single test to remove all dependencies. However I have multiple test suites with this behavior, and some of the tests would get really big and unwieldy.
So, how can I keep the dependencies between small test cases while using data providers? I'm using php 5.5 along with Silex 1.3 and phpunit 4.8
EDIT: I should mention that my tests are extending Silex' WebTestCase, though I'm not sure if it makes a difference.
Here's an example in case I was unclear:
public function testValidResponse()
{
$client = $this->createClient();
$client->request('POST', '/foo', $this->params);
$this->assertEquals(200, $client->getResponse()->getStatusCode());
return $client->getResponse();
}
/**
* #depends testValidResponse
*/
public function testStatusIsOk(Response $response)
{
$json = json_decode($response->getContent(), true);
$this->assertTrue($json['status']);
return $json;
}
/**
* #depends testStatusIsOk
*/
public function testExecutionTime($json)
{
$this->assertLessThan($this->maxExecutionTime, $json['debug']['executionTimeSec']);
}
/**
* #depends testValidResponse
*/
public function testAnotherEndpointValidResponse()
{
$client = $this->createClient();
$client->request('GET', '/bar');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
return $client->getResponse();
}
/**
* #depends testAnotherEndpointValidResponse
*/
public function testSideEffectsFromFirstTest(Response $response)
{
// ...
}
I think the main problem is that the tests are too complex, and should not depend that much on each other. The solution was to reduce some of the complexity by refactoring the tests. Here's a rough outline of what I did:
Moved some of the more complex test cases from Integration testing
to Unit testing. So I'm not testing the endpoint any more, but the methods that are executed when you go to the endpoint.
Added a generic test case that operates on data from a data provider which includes, not exclusively, the urls to the different endpoints. This test case simply tests that the endpoints are returning the expected http code and some other small things. This allowed me to put all of my Integration testing into one simple test case.
I'm pretty happy with the solution, I deleted 7-8 test classes, and the complex testing that got moved to Unit testing is also simplified.
I'm building a monitoring solution for logging PHP errors, uncaught exceptions and anything else the user wants to log to a database table. Kind of a replacement for the Monitoring solution in the commercial Zend Server.
I've written a Monitor class which extends Zend_Log and can handle all the mentioned cases.
My aim is to reduce configuration to one place, which would be the Bootstrap. At the moment I'm initializing the monitor like this:
protected function _initMonitor()
{
$config = Zend_Registry::get('config');
$monitorDb = Zend_Db::factory($config->resources->db->adapter, $config->resources->db->params);
$monitor = new Survey_Monitor(new Zend_Log_Writer_Db($monitorDb, 'logEntries'), $config->projectName);
$monitor->registerErrorHandler()->logExceptions();
}
The registerErrorHandler() method enables php error logging to the DB, the logExceptions() method is an extension and just sets a protected flag.
In the ErrorController errorAction I add the following lines:
//use the monitor to log exceptions, if enabled
$monitor = Zend_Registry::get('monitor');
if (TRUE == $monitor->loggingExceptions)
{
$monitor->log($errors->exception);
}
I would like to avoid adding code to the ErrorController though, I'd rather register a plugin dynamically. That would make integration into existing projects easier for the user.
Question: Can I register a controller plugin that uses the postDispatch hook and achieve the same effect? I don't understand what events trigger the errorAction, if there are multiple events at multiple stages of the circuit, would I need to use several hooks?
Register your plugin with stack index 101. Check for exceptions in response object on routeShutdown and postDispatch.
$response = $this->getResponse();
if ($response->isException()) {
$exceptions = $response->getException();
}
to check if exception was thrown inside error handler loop you must place dispatch() in a try-catch block
The accepted answer by Xerkus got me on the right track. I would like to add some more information about my solution, though.
I wrote a Controller Plugin which looks like that:
class Survey_Controller_Plugin_MonitorExceptions extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$response = $this->getResponse();
$monitor = Zend_Registry::get('monitor');
if ($response->isException())
{
$monitor->log($response);
}
}
}
Note that you get an Array of Zend_Exception instances if you use $response->getException(). After I had understood that, I simply added a foreach loop to my logger method that writes each Exception to log separately.
Now almost everything works as expected. At the moment I still get two identical exceptions logged, which is not what I would expect. I'll have to look into that via another question on SO.