Setup Testing and Fixtures in Symfony2: The Easy Way
Setting up testing and fixtures in Symfony2 is vital if you plan on starting Test Driven Development or you simply want to start covering your code with properly written tests that can access mock data.
1. Install PHPUnit and php 5.6
The first thing you need to do is to install PHPUnit on your machine:
$ wget https://phar.phpunit.de/phpunit.phar $ chmod +x phpunit.phar $ sudo mv phpunit.phar /usr/local/bin/phpunit $ phpunit --version
and then, if needed, also upgrade your PHP version to 5.6:
$ sudo apt-get install language-pack-en-base $ export LC_CTYPE="en_US.UTF-8" $ sudo add-apt-repository ppa:ondrej/php5-5.6 $ sudo apt-get update $ sudo apt-get install php5
and make sure everything's ok by running: phpunit -c app/
Please note that this is not a testing tutorial so if you'd like to learn more about how to actually test your Symfony2 app, then please read their documentation.
2. Setup and create a test database
In order to be able to configure testing and fixtures in a Symfony2 app a separate, independent database is needed so that your dev environment is not affected.
In the config_test.yml
file you simply need to add:
doctrine: dbal: host: 127.0.0.1 dbname: testdb user: [YOUR_MYSQL_USERNAME] password: [YOUR_MYSQL_PASSWORD]
then simply run php app/console doctrine:database:create --env=test
Related: Send Emails in Symfony2: The Right Way
3. Build the BaseTestSetup class
Once our test database is ready we can start building our BaseTestSetup
class, one which will serve as parent for all of our tests.
<?php namespace AppBundle\Tests; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; abstract class BaseTestSetup extends WebTestCase { protected $client; protected $container; protected $em; protected function setUp() { $this->client = static::createClient(); $this->container = $this->client->getContainer(); $this->em = static::$kernel->getContainer() ->get('doctrine') ->getManager(); } }
4. Install the LiipFunctionalTestBundle
Even though Symfony does have an out of the box solution to setup and test your app, the LiipFunctionalTestBundle provides base classes for functional tests to assist in setting up testing and fixtures and HTML5 validation.
After you install and configure the bundle, go back to your BaseTestSetup
class and make the necessary modifications:
<?php namespace AppBundle\Tests; use Doctrine\ORM\Tools\SchemaTool; use Liip\FunctionalTestBundle\Test\WebTestCase; abstract class BaseTestSetup extends WebTestCase { protected $client; protected $container; protected $em; protected function setUp() { $this->client = static::createClient(); $this->container = $this->client->getContainer(); $this->em = static::$kernel->getContainer() ->get('doctrine') ->getManager(); if (!isset($metadatas)) { $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); } $schemaTool = new SchemaTool($this->em); $schemaTool->dropDatabase(); if (!empty($metadatas)) { $schemaTool->createSchema($metadatas); } $this->postFixtureSetup(); $this->loadFixtures(array( 'AppBundle\DataFixtures\ORM\LoadUserData', )); } }
The new code above simply drops and creates the database each them tests run. Then it loads the fixtures that you'll need. The LoadUserData
class does not exist yet so we'll go right ahead and add it by following the handy tutorial found on Symfony's website.
5. Write the very first test
Now that have your fixtures ready, you can go ahead and write your first test. Create a new file in your AppBundle/Tests
folder called UserTest.php
, next to BaseTestSetup.php
:
<?php namespace AppBundle\Tests; use AppBundle\Tests\BaseTestSetup; class UserTest extends BaseTestSetup { public function testSuggestImprovementEmail() { // assuming that you named your user 'username' in your Fixtures $crawler = $this->client->request('GET', '/show/username'); $this->assertGreaterThan( 0, $crawler->filter('html:contains("Hello first user!")')->count() ); } }
And that's about it! You now have everything you need to properly test your awesome app.
How To Mock Endpoints in Automated Acceptance Tests
The purpose of acceptance testing is to reproduce production environment as much as possible in order to evaluate the software in a 'real-world' situation similar to what the customers will experience and this includes using real endpoints.
But using real endpoints has some disadvantages. In automated acceptance tests, the endpoint will be stressed out and this is not performance testing. Also, you must use production data (even usernames and passwords) and this is not good because you might break something there. The list with the reasons could go on and on and this is why you should mock some endpoints in automated acceptance tests. Also, you should keep your endpoint's URL stored in environment variables to be able to change it easily (production URL or mock URL).
I will show you some basic examples of mocking endpoints written in Ruby, Python and GO.
Mock endpoint in Ruby
I have used Sinatra (DSL for quickly creating web applications in Ruby) which is very easy to use.
Install the gem:
gem install sinatra
#myapp.rb require ‘json’ require ‘sinatra’ get '/:username' do if params[:username] != 'your_username' status 404 else content_type :json {'message' => 'success'}.to_json end end
ruby myapp.rb -o 0.0.0.0 -p 5000 > /dev/null 2>&1 &
This will start the process in background and your endpoint on http://localhost:5000.
If you make a GET request on http://localhost:5000/your_username you will get a ‘success’ message, else a 404 status code.
Mock endpoint in Python
In Python it is very easy to create an endpoint with Flask.
To install it you have to run:
pip install flask-restful
#myapp.py #!flask/bin/python from flask import Flask, jsonify from flask import Response import json app = Flask(__name__) @app.route("/<username>", methods=['GET']) def put_username(username): if username == 'your_username': resp = Response("success!\n", mimetype='application/json') else: resp = Response("", status=404, mimetype='application/json') return resp if __name__ == '__main__': app.run(debug=True)
As you can see, this does the exact same thing as the endpoint created in Ruby.
You simply run it with
python myapp.py
Mock endpoint in GO
//myapp.go package main import ( "fmt" "net/http" "github.com/julienschmidt/httprouter" ) func username(w http.ResponseWriter, r *http.Request, p httprouter.Params) { if p.ByName("username") == "your_username" { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) fmt.Fprint(w, `{"message":"success"}`) } else { w.WriteHeader(404) } } func main() { r := httprouter.New() r.GET("/:username", username) http.ListenAndServe("localhost:5000", r) }
This does the same thing as the previous two endpoints and you run it with:
go run myapp.go
Thanks for reading this. What other ways of mocking an endpoint did you find?
How to Fix Common Errors When Testing in RSpec
If you are a Software Test Engineer or Quality Control Engineer and you want to automate your API call tests, then you should try RSpec (Ruby's testing framework). I didn’t exactly chose it (the QC team was already using it), but I tend to believe that I would have picked it in the future for my own tests because when it comes to the installation of the program, the process is not that complicated at all.
How to Fix Common Errors When Testing in RSpec
Actually, let me show you how little you need to do in order to start writing your own tests:
gem install rspec rspec --init
Yep, that’s all. Now you can automate your tests and run them with the following command:
rspec your_test_suite.rb
I will now show you some common error messages that I've encountered, so that you can avoid them during your work. These errors are caused by very small mistakes, but usually in the rush of delivering quality we skip some things or words.
1. syntax error, unexpected keyword_end, expecting end-of-input (SyntaxError)
Let’s take a look at the following examples:
describe 'Test Suite' it 'Validate successful response' do response = RestClient.get('www.intelligentbee.com') expect(response.code).to eq(200) end end
describe 'Test Suite' do it 'Validate successful response' response = RestClient.get('www.intelligentbee.com') expect(response.code).to eq(200) end end
So, if you get the above error, you most probably forgot to put a ‘do’ after ‘describe’ or ‘it’ methods.
2. syntax error, unexpected end-of-input, expecting keyword_end (SyntaxError)
I will use the same example again:
describe 'Test Suite' do it 'Validate successful response' do response = RestClient.get('www.intelligentbee.com') expect(response.code).to eq(200) end
What is wrong with this? Well, I missed an ‘end’. I’ll take it you can figure out by yourself where it should be placed.
3. JSON::ParserError: 757: unexpected token
Take a look:
describe 'Test Suite' do it 'Validate successful response' do response = RestClient.get('www.intelligentbee.com') parsed_response = JSON.parse(response) expect(parsed_response['message']).to eq "Some message" end end
Supposedly, sometimes you will get as an answer a JSON and you will want to parse it for better tests. You will get the above error if the answer is not a JSON and the parser can’t find there what it expects.
I hope you will find this short guide useful, I plan to continue writing about common errors that we may encounter while using RSpec.
What Is the Difference between QA and QC/Software Testing
If you work in IT or, at least, had any experience in this area, you definitely know that there are multiple terms to define the testing world. The biggest competitors here are QA (Quality Assurance) and QC (Quality Control) which is basically the same as Software Testing.
Let's see how these are defined:
Quality Assurance (QA) is a part of quality management focused on providing confidence that quality requirements will be fulfilled. [ISO 9000]
Quality Control/Testing is a process that consisting of all the life cycles activities, both static and dynamic, concerned with planning, preparing and evaluating software products and related work products. It tries to determine if they satisfy specified requirements in order to demonstrate that they are fit for purpose and to detect defects. [ISTQB glossary]
Quality Assurance
This is about process oriented and preventive activities. It means that these activities are focused on improving the software development process and that the system will meet its objectives. The QA Engineer is active throughout the product’s lifecycle and communicates with all the people involved in the process, from Project Manager to Software Developer and QC Engineer. The QA analyses and seeks the continuous improvement of both process and product while ensuring all tasks demonstrate appropriate quality and that are finished on time.
Quality Control/Software testing
Well, this is about product orientated activities and it is a corrective process (testing is a process rather than a single activity - there are a series of activities involved). The QC Engineer must find bugs in the system before users do, investigate and report on how well the software performs relative to its expectations and is generally active at the end of a coding cycle.
Conclusions
QA and QC both have to make the software better, however, QA is about process orientated and preventive activities, while QC involves a corrective process and product orientated activities.