I've been using the Behat english-like test language (Gherkin?) to write test scripts but have quickly come up it's significant limitations.
If I could execute these tests in PHP within the phpunit test scripts that I have set up I could significant expand the tests that I could add. (I'm using FuelPHP).
I've been tinkering around for a few hours trying to get Behat to execute inside a PHPUNIT test script but have not had much luck.
Is this possible?
I think you are confusing something, because what you are saying doesn't make a lot of sense. If you having a hard time expressing the logic with the code, you should ask a specific question on that.
Behat and Mink are both written in PHP, you write your contexts in PHP, there is a gadzillion of plugins to make the life easier (also written in php). As the matter of fact, all your tests are executed in PHP when you run them… Yup!
If you want to compare data from two pages you can simply create a step like this:
/**
* #Then /^the page "(.+)" and the page "(.+)" content should somehow compare$/
*/
public function assertPageContentCompares($page1, $page2)
{
$session = $this->getSession();
$session->visit($page1);
$page1contents = $session->getPage()->getHtml();
$session->visit($page2);
$page2contents = $session->getPage()->getHtml();
// Compare stuff…
}
Besides the obvious, you can use PHPUnit in with Behat / Mink to make the assertions, i.e., in your step definitions. Most (in not all) PHPUnit assertions are static methods, using them is as simple as this:
PHPUnit_Framework_TestCase::assertSame("", "");
You can use Selenium (probably other frameworks too) with PHPUnit, if this is more about unit testing than functional testing, the official documentation tells how.
If you simply hate Gherkin then there's not much you can do with Behat – it's at the core of it. With PhpStorm 8 out there is a pretty good support for it, you can easily navigate around your code and refactor it quickly. If that doesn't cut it, there's another great alternative to Behat called Codeception, where you use pure PHP to define your tests. Maybe that's what you are looking for.
Yes. You can use the library I created: jonathanjfshaw/phpunitbehat.
Your phpunit tests will looks like this:
namespace MyProject\Tests;
use PHPUnit\Framework\TestCase;
use PHPUnitBehat\TestTraits\BehatTestTrait;
class MyTestBase extends TestCase {
use BehatTestTrait;
}
namespace MyProject\Tests;
class MyTest extends MyTestBase {
protected $feature = <<<'FEATURE'
Feature: Demo feature
In order to demonstrate testing a feature in phpUnit
We define a simple feature in the class
Scenario: Success
Given a step that succeeds
Scenario: Failure
When a step fails
Scenario: Undefined
Then there is a step that is undefined
FEATURE;
/**
* #Given a step that succeeds
*/
public function aStepThatSucceeds() {
$this->assertTrue(true);
}
/**
* #When a step fails
*/
public function aStepFails() {
$this->assertTrue(false);
}
}
I wrote a blog post explaining why I think this is not a bad idea.
Related
I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.
I have a function, which someone else wrote, that creates a cURL wrapper object inside the function. Simplified version below
public function getCodes()
{
//do some stufff
$communicator = new Communicator();
$result = $communicator->call($this->API_KEY);
//do some stuff with $result
}
I was tasked with learning PHPUnit and writing tests for this type of code. In doing so I found that it's really hard to test a function like this when the object is created inside of the function and also that tests shouldn't require any outside communication to work.
We wanted to push our tests to git as many projects do but we didn't want to accidentally or intentionally push our API credentials to git.
So my solution was to keep getCodes() public but make it a wrapper for a private function that accepts a Communicator object as a parameter. Then I could test the private method with a mock Communicator object.
But this would mean that getCodes is never tested (my boss wants 100% code coverage) and I also read that you shouldn't be writing tests for private functions in most circumstances.
So my question is basically, how do I write a test for a function like this with an API call.
I would really suggest rewriting the code to inject the Communicator object via constructor.
If you already see there is a big issue with writing tests for something it is a very strong signal to re-thing the current implementation.
Another thing is that you shouldn't test your privates. Sebastian Bergmann wrote a post on his blog about this some time ago and the conclusion is - it is possible just not good (https://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html).
Completely different thing is that I think your tests shouldn't go outside of the boundaries of your system. That is - mock everything that connects to outside systems. Such tests may fail for various of reasons non of witch are valid from the sole perspective of running tests.
You also mentioned coverage. Unfortunately this is something where, I hope, everyone will agree - you cannot have it the moment you start using native PHP resources (with some small exception like FS). You have to understand that things like curl, ssh, ftp and so on cannot be unit tested.
I am successfully using Behat 3.0 with the tests defined in Feature files, using Gherkin language. However, in some cases, it would be useful to define the steps programatically - Gherkin is readable, but difficult to define multiple variants.
Is there a way to programatically define the test steps (in PHP classes), so these can be picked up by Behat? I have found ArrayLoader class, which seems to be able to do that. However, I wasn't able to make it working with Behat. It seems Behat is using Gherkin FileLoader by default and I haven't found a way to rewrite this behavior (or rather extend) in the config file.
How can I combine test input from Gherkin files with custom definitions specified in PHP files?
Is there a way to programatically define the test steps (in PHP
classes),...
If I don't misunderstand what you want, you can do like this:
use Behat\Behat\Definition\Call\Then;
use Behat\Behat\Definition\Call\When;
use Behat\MinkExtension\Context\MinkContext;
class FeatureContext extends MinkContext
{
public function iWaitSeconds($second)
{
new Then(.....);
new When(.....);
new Given(.....);
}
}
You need to do a bit of research for more examples.
e.g.: new When("The content is:", new PyStringNode($string));
I'm using the Selenium2TestCase object to run some tests on a site. I'm trying to modularise these tests for ease of writing and new tests and readability.
With my limited knowledge of Selenium WD and PHPUnit and a distinct lack of useful information available online, I've jumped in and start by splitting my tests up into separate 'page' objects. I don't know whether this is the best solution or if its even technically possible.
This is a simplified version of what I have at the moment...
class Selenium2TestCase_Extension extends PHPUnit_Extensions_Selenium2TestCase
{
public function testPages(){
$pages = array('about', 'contact');
foreach($pages as $page){
// Can I instantiate and run the tests in About_Test from here???
}
}
}
class About_Test extends PHPUnit_Extensions_Selenium2TestCase
{
public function testTitle(){
$this->url('/about');
$this->assertEquals("About Us", $this->title());
}
}
class Home_Test extends PHPUnit_Extensions_Selenium2TestCase
{
public function testTitle(){
$this->url('/home');
$this->assertEquals("Welcome", $this->title());
}
}
I'm not sure if I'm attempting the impossible here or what so any help would be much appreciated!
Yes, you can it with exec or system, or something similar. But it seems that is not proper way.
Is it XY problem?
You could
use groups for launching some of tests.
store those tests in one separate folder and run test over it using command-line interface.
run only wanted tests using command-line interface.
(in your case) use data provider and write one test only.
I am developing an API documentation system, and want to dynamically check that each command has documentation attached.
The easiest way to do this is dynamically loop through each command and check for existing documentation to match it.
My code looks like this:
public function testMissingDocs()
{
foreach ($aCommands as $sKey => $aOptions)
{
$this->assertNotNull($oDocs->get($sKey));
}
}
The problem with this is the StopOnFailure/Error feature of PHPUnit which stops the test after the first assertion fails. I understand the reasons for this functionality and I want to keep it on for the majority of my test cases, but for dynamic assertions/tests it makes things a bit hard.
Is there a way to disable it on a per-test basis so I can check each command in this test?
You can use a data provider to split the single test into as many tests as you have commands.
/**
* #dataProvider getDocsForAllCommands
*/
public function testEveryCommandHasDocs($sKey)
{
$this->assertNotNull($oDocs->get($sKey));
}
public function getKeysForAllCommands()
{
return array_keys($aCommands);
}
If the documentation for a particular class or method is missing, that would represent a problem with that class, not with the method to retrieve the documentation.
Although it is probably easier to combine all of the documentation into a single test, that does not follow unit testing best practices (and hence is why the PHPUnit framework is working against you rather than for you).
I would suggest one of two approaches to rectify the issue:
Refactor your tests so that each class and/or method has its own documentation check (there are a few ways you can run multiple tests in one execution).
Use a tool such as PHP_CodeSniffer instead, as lack of documentation could be considered a convention – rather than a functional – defect.