Buy
Buy

Yo friends! Oh, SO glad you've made it for our phpspec tutorial! You will not regret it. Thing number one to know about phpspec is: it's... just... fun!

Ok, but what is phpspec? It's a unit testing tool... exactly like phpunit. Wait... that's not totally right. If you watched our PHPUnit tutorial, then you know that PHPUnit is a perfectly fine tool for unit testing your code. So... why are we even talking about phpspec?

Here's the truth: yes, phpspec is a tool for unit testing your code. But, that's not its main job. Nope, it's a tool for helping you design your code in a well-organized, meaningful and maintainable way. You probably already think about the design and user experience of your front-end. But, have you ever thought about the design and experience of your PHP classes?

That's phpspec's job. And yes, as a nice by-product, you will get unit tests. And as a nicer by-product, you will also enjoy the process - coding with phpspec is fun. Oh, and later - we'll talk about how phpspec & PHPUnit fit together - like should we use both in the same app? Short answer: yes!

Starting Point: Empty Project!

Ok, let's go! Just like in our PHPUnit tutorial, we're going to design & build a dinosaur park - complete with T-Rex, stegosaurus, enclosures for our dinosaurs and, with any lucky, some security systems that - thanks to our tests - won't fail as soon as a storm rolls in or a developer leaves early for lunch.

To make sure our dinosaurs don't once again rule the Earth, you should totally code along with me. Download the course code from this page. When you unzip it, you'll find a start/ directory with the same code that you see here. But... well... what we have here is... nothing! Just an empty project with a composer.json file that also... has nothing important inside. This tutorial directory does have a few files that we'll use later - so make sure you have it.

We're starting with an empty project because phpspec is truly a framework-agnostic library. But don't worry - if you're a Symfony user, we'll build a structure that will be very familiar to you - with the same directories and namespaces as a Symfony app.

Installing phpspec

To get phpspec installed, open a terminal, move into the project, close Facebook, and run:

composer require phpspec/phpspec --dev

And.... ding! Just like with PHPUnit, installing phpspec means that you get a new, shiny executable! Run:

./vendor/bin/phpspec

The phpspec executable really only has two commands: describe and run. And we'll talk about both of them very soon.

Configuration autoload in composer.json

But first, we need just a little bit of configuration to get things working. The first piece of configuration... has nothing to do with phpspec at all! Our app has no PHP classes yet. But when we add some, I want to put them in the src/ directory and prefix each namespace with App. That will be exactly like a Symfony project.

Open composer.json. To make sure Composer's autoloader knows where our classes live, we need to add some config here. This is code that you normally get automatically when you start, for example, a new Symfony project. But I want to show how it's done by hand so that we can truly understand what's going on behind the scenes.

Add autoload, then psr-4, then say that classes starting with App\\ will live in the src/ directory.

13 lines composer.json
{
... lines 2 - 6
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}

To make Composer notice this change, find your terminal and run:

composer dump-autoload

Autoloading... done!

Configuring phpspec

Next, one of my favorite things about phpspec is that it generates code for you! But to do that, it also needs to know that our classes will live in the src/ directory and that each namespace will start with App. Unfortunately, phpspec can't automatically get all this info from composer.json, but it's no problem.

Create a phpspec.yml file at the root of the project - phpspec automatically knows to look for this. Inside add suites then default. Like most testing tools, you can organize your tests into multiple groups, or "suites" if you want. In this tutorial, we'll stick to using the one, "default" suite.

5 lines phpspec.yml
suites:
default:
... lines 3 - 5

Under this, add namespace: App - because all of our classes will start with the App namespace - and psr4_prefix: App. Those two lines are enough to help phpspec know where to generate our files.

5 lines phpspec.yml
... lines 1 - 2
namespace: App
psr4_prefix: App

And... team, we're ready to go! Next, let's create our first specification... ooOOOOooo. That's the file where we will describe how a single class should behave by writing examples. Woh.

Leave a comment!

  • 2019-03-18 weaverryan

    Ah, just saw this! So you found the same thing I did, but sooner ;). Nice job!

  • 2019-03-18 weaverryan

    Hey Diaconescu Petrisor!

    Ah, ok, it's much more clear now :). Two things:

    1) I wasn't able to test out the POST /api/battles easily, because I would need to create a programmer (and project) first, and a programmer requires a User first, but there is no endpoint for creating users via the API.. and no data fixtures. Could you add some data fixtures to AppFixtures so there's some test data?

    2) About the "swagger_context" part, I think I know why: the Battle entity doesn't have setProgrammer() or setProject() methods, which makes ApiPlatform think that these are read-only fields and can't be set. In fact, you CAN have constructor args (I think?) but you nee to do some custom stuff.

    Cheers!

  • 2019-03-17 Diaconescu Petrisor

    I found the response. I must have setters on project and programmer property.

  • 2019-03-12 Diaconescu Petrisor

    I didn't explain enough. Did you try something like this CreateBattleImgurPhoto? I have this error ErrorCreateBattleImgurPhoto. I didn't expect to complain about the irl. This paths surely exists and I created the project beforehand to give it to the battle. I expected an error, of course, but something like foughtAt can't be null.
    I don't know why his problem is the IRL.
    I put the entire response in the last commit of the repository.
    I tried to remake the entire work in another project but I have the same problem. Apart of that I didn't understood why I had to resort to swagger_context to have programmer and project in body request of the post route battle. I didn't make any setup to have user in body request of the post programmer route appart of using ApiResorce at the top of the Programmer class. I didn't use any swagger_context. I don't see why the both situations aren't similar and why I had to made different things to achieve similar results.

    Cheers! Thank you for imgur idea, I didn't know about it.

  • 2019-03-11 weaverryan

    Hey Diaconescu Petrisor!

    Hmm, I just cloned the repo - it's working perfectly for me! https://imgur.com/a/Y05Yped

    However, I did notice one strange thing. When I ran "composer install", Symfony flex executed a bunch of recipes, which modified a bunch of files: https://imgur.com/a/BiqHCTA

    That should not happen. When you originally installed the libraries, the Flex recipes should have executed, and these files should have been created/modified on your system (and your symfony.lock file should have been updated to record that these recipes had been executed). So, something is going wrong with the recipe process, which is making it so that you're missing some files that are probably causing your problem.

    I hope this helps!

    Cheers!

  • 2019-03-08 Diaconescu Petrisor

    Cheers weavarryan!
    I have a new repository at https://github.com/petre-sy...
    In the last commit I added ApiResource to Battle and Project resources.
    But in POST api/battles there's no project and programmer resources in body request when I navigate to https://localhost:8443/api is only {}
    In POST api/programmers I had even when I put annotation ApiResource above the Programmer class "user":"string" in body request model. user is ManyToOne relation betwen User and Programmer, project is ManyToOne relation betwen Battle and Project and programmer ManyToOne relation between Battle and Programmer.
    What to do to get them there and make this to work?

  • 2019-03-05 Diaconescu Petrisor

    Cheers!

    I discovered that the hard way ultimately. The only line I could not remove by no means was the line with html. I have another repository that has incorporated this line too. Thank you anyway.

  • 2019-03-04 weaverryan

    Hey Diaconescu Petrisor !

    Also add this to your formats:


    html: [text/html]

    The reason is that you're overriding the formats, so you're replacing the existing, default formats, which you can see here :) https://github.com/api-plat...

    Cheers!

  • 2019-03-01 Diaconescu Petrisor

    Cheers
    Sorry, I thought that this question would eventually reach to weaverryan.
    Ok, my question is not related to this episode and is in completion to my previous question picked by weavarryan, in some respect.
    I have one repository on github (https://github.com/petre-sy... where I have all the code. Here is one ancient course reworked on novel grounds. The last commit explained where the problem arise. We can download the code from there.

    Thank you.

  • 2019-02-28 Diego Aguiar

    Hey Diaconescu Petrisor

    I'm not sure I'm following you. What problem are you expieriencing? We can't access to your localhost, so we can't see your commits, and what's the relation of API Platform to this episode?

  • 2019-02-27 Diaconescu Petrisor

    I manage to make the test to behave correctly.
    I made all the modifications in repository. They were some mistypes somewhere but I eradicate them.
    But remain one unnerving problem. This time reffer to the interface displayed when navigate to https://localhost:8443/api after I type in console 'APP_ENV=test symfony serve --port=8443'. I try to enable hal+json format in api_platform.yaml. In the last commit you'll see two photos one is before to apply the changes in api_platform.yaml and the other one is after. Should be largely the same image. It isn't. I don't know what I miss.

  • 2019-02-25 weaverryan

    Hey Diaconescu Petrisor !

    Thanks for sharing the code - it always makes my life a lot easer :). So, you were SUPER close - there was one tiny detail:


    // YOU had this

    /**@BeforeScenario */
    public function gatherContexts(BeforeScenarioScope $scope){

    // THIS is what works
    /** @BeforeScenario */
    public function gatherContexts(BeforeScenarioScope $scope){

    Yep, it's that tiny detail - without the space, it's not a valid annoation syntax, and so your method was never recognized and called. Oh, and there was one other small thing once you get this working - this time in FeatureContext:


    -return $this->getContainer()->get('doctrine.orm.entity_manager')->getEntityManager();;
    +return $this->getContainer()->get('doctrine.orm.entity_manager');

    I hope that helps! Cheers!

  • 2019-02-22 Diaconescu Petrisor

    Cheers!
    I try to rework rest series based on api-platform this time and tested with behat.
    But examples for sharing multiple contexts in another context doesn't seem to work anymore.
    The entire example is found in https://github.com/petre-sy... repository with all the detailed steps.
    In the last commit I want to share the context from FeatureContext into ApiExtendedContext. ApiExtendedContext rely on ApiContext from Behat Imbo extension but this extension doesn't have the ability to provide processing for steps that have payloads like:
    """
    {
    "nickname": "JavaProgrammer",
    "avatarNumber": 5
    "user": "api/users/%users|weaverryan@google.com|id%"
    }
    """

    So i invented PayloadProcess class to deal with that. I added a new step 'Given I have the request body' to ApiExendedContext to permit body requests that have the respective syntax included. Probably I'll add something 'I request for in the future. I want to keep all the benefits from Imbo Behat extension so I copied the code in 'Given the request bod is' step from Imbo Behat extension and I made minor adjustments to the former method.
    But PayloadProcess that I try to use in ApiExendedContext needs EntityManager. I tried to get it from FeatureContext but all I get when run: vendor/bin/behat is:
    Fatal error: Call to a member function getEntityManager() on null (Behat\Testwork\Call\Exception\FatalThrowableError)
    Obviously I miss something. How must be remade this code to make it work? The API works as expected . I checked in http://localhost:8443/api interface.

    Thank you in advance.

    The first two REST course series based on raw Symfony are fully working now with all the featues of the original ones.
    They are availabe at:
    https://github.com/petre-sy...
    and https://github.com/petre-sy...

  • 2019-01-10 weaverryan

    Yea! Awesome! Thanks for letting me know Diaconescu Petrisor! I'm still getting used to this new tool myself :).

    Happy new year to you too!

  • 2019-01-09 Diaconescu Petrisor

    Happy new year, thank you and cheers, all at once. Indeed it works like in the api-platform example

  • 2019-01-07 weaverryan

    Hey Diaconescu Petrisor!

    Let me give you a quick recommendation - and we can see if it helps :). Instead of setting up Nginx, try using the new Symfony console tool - you can see how to get it on this page: https://symfony.com/download

    This tool allows you to create https servers without any issues - e.g.


    symfony serve --port=8443

    This should start an https web server in that project.

    It's a super new tool - but it should make your life easier :). A lot of the commands/features are only documented in the commands themselves. For example:


    # see all commands
    symfony list

    # get info about any command
    symfony serve --help

    Let us know if this helps!

    Cheers!

  • 2019-01-07 Diaconescu Petrisor

    What I am to ask is not linked to phpspec.
    I discovered a new tool for api designing at the site https://api-platform.com/do....
    I try to follow the chapter [Using Symfony Flex and Composer (advanced users)]
    I use Ubuntu 16.04 for development and nginx as web server. My problem is when I want to serve the page https://localhost:8443/
    I never set ssh connection before so I'm not sure I have to do. I respected directions from https://www.digitalocean.co... as far as regarding nginx+ssh and symfony docs for the rest. So my nginx now looks like so:
    server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name localhost;
    root /home/---/www/Symfony/bookshop-api/public;
    include snippets/self-signed.conf;
    include snippets/ssl-params.conf;

    # SSL configuration
    #
    listen 8443 ssl http2 default_server;
    listen [::]:8443 ssl http2 default_server;

    location / {
    # try to serve file directly, fallback to index.php
    try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
    fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    fastcgi_split_path_info ^(.+\.php)(/.*)$;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $realpath_root;

    }

    # return 404 for all other php files not matching the front controller
    # this prevents access to other php files you don't want to be accessible.
    location ~ \.php$ {
    return 404;
    }

    error_log /home/----/logs/nginx/symfony_error.log;
    access_log /home/----/logs/nginx/symfony_access.log;
    }

    With these setings htt content is served OK but when i type:https://localhost:8443/ as it's expected I hit the privacy error page(Your connectiois not private). I don't know how to do from here.
    In api-platform docs says "You'll need to add a security exception in your browser to accept the self-signed TLS certificate that has been generated for this container when installing the framework. " Which framework is reffered? api-platform ? symfony? This TLS certificate must be generated itself ? Is already generated ? Where it is? How I add this mentioned security exception in google chrome? In chrome I found in private and security settings "Manage certificates" option. I presume I must follow the tab "Your certificates" and Import the aforementioned TLS certificate. I think right? And again where is this TLS certificate?