mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
152 lines
6.3 KiB
Markdown
152 lines
6.3 KiB
Markdown
|
# How To Create a Sapphire Test
|
||
|
|
||
|
A unit test class will test the behaviour of one of your DataObjects. This simple fragment of SiteTreeTest provides us
|
||
|
the basics of creating unit tests.
|
||
|
|
||
|
:::php
|
||
|
<?php
|
||
|
|
||
|
/**
|
||
|
|
||
|
* Tests for SiteTree
|
||
|
*/
|
||
|
class SiteTreeTest extends SapphireTest {
|
||
|
/**
|
||
|
|
||
|
* Define the fixture file to use for this test class
|
||
|
*/
|
||
|
static $fixture_file = 'sapphire/tests/SiteTreeTest.yml';
|
||
|
|
||
|
/**
|
||
|
|
||
|
* Test generation of the URLSegment values.
|
||
|
* - Turns things into lowercase-hyphen-format
|
||
|
* - Generates from Title by default, unless URLSegment is explicitly set
|
||
|
* - Resolves duplicates by appending a number
|
||
|
*/
|
||
|
function testURLGeneration() {
|
||
|
$expectedURLs = array(
|
||
|
'home' => 'home',
|
||
|
'staff' => 'my-staff',
|
||
|
'about' => 'about-us',
|
||
|
'staffduplicate' => 'my-staff-2',
|
||
|
'product1' => '1-1-test-product',
|
||
|
'product2' => 'another-product',
|
||
|
'product3' => 'another-product-2',
|
||
|
'product4' => 'another-product-3',
|
||
|
);
|
||
|
|
||
|
foreach($expectedURLs as $fixture => $urlSegment) {
|
||
|
$obj = $this->objFromFixture('Page', $fixture);
|
||
|
$this->assertEquals($urlSegment, $obj->URLSegment);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
There are a number of points to note in this code fragment:
|
||
|
|
||
|
* Your test is a **subclass of SapphireTest**. Both unit tests and functional tests are a subclass of SapphireTest.
|
||
|
* **static $fixture_file** is defined. The testing framework will automatically set up a new database for **each** of
|
||
|
your tests. The initial database content will be sourced from the YML file that you list in $fixture_file. You must
|
||
|
define this value. Note also that, for the time being, you can only point to one YML file for each test class.
|
||
|
* Each **method that starts with the word "test"** will be executed by the TestRunner. Define as many as you like; the
|
||
|
database will be rebuilt for each of these.
|
||
|
* **$this->objFromFixture($className, $identifier)** can be used to select one of the objects named in your fixture
|
||
|
file. To identify to the object, we provide a class name and an identifier. The identifier is specified in the YML
|
||
|
file but not saved in the database anywhere. objFromFixture() looks the DataObject up in memory rather than using the
|
||
|
database. This means that you can use it to test the functions responsible for looking up content in the database.
|
||
|
* **$this->assertEquals()** is one of the many assert... functions that PHPUnit provides us. See below for more
|
||
|
information.
|
||
|
|
||
|
|
||
|
## Assertion commands
|
||
|
|
||
|
**$this->assertEquals()** is an example of an assertion function. These functions form the basis of our tests - a test
|
||
|
fails if and only if one or more of the assertions fail.
|
||
|
|
||
|
|
||
|
There are many assertions available:
|
||
|
|
||
|
* See [the PHPUnit manual chapter 22](http://www.phpunit.de/manual/current/en/api.html#api.assert)
|
||
|
for a listing of all PHPUnit's built-in assertions.
|
||
|
* **$this->assertEmailSent($to, $from, $subject, $content)**: When an email is "sent" during a test run, it's not
|
||
|
actually sent. Instead, it is logged in an internal register. You can use assertEmailSent() to verify that an email
|
||
|
was sent. Each of the arguments can be a string, for an exact match, or, a preg_match() compatible regular expression,
|
||
|
if it starts with "/".
|
||
|
|
||
|
## The Database YAML file
|
||
|
|
||
|
The main feature of SapphireTest over the raw PHPUnit classes is that SapphireTest will prepare a temporary database for
|
||
|
you. The content of that database is provided in a special YAML file. YAML is a simple markup languages that uses tabs
|
||
|
and colons instead of the more verbose XML tags, and because of this much better for developers creating files by hand.
|
||
|
|
||
|
We will begin with a sample file and talk our way through it.
|
||
|
|
||
|
Page:
|
||
|
home:
|
||
|
Title: Home
|
||
|
about:
|
||
|
Title: About Us
|
||
|
staff:
|
||
|
Title: Staff
|
||
|
URLSegment: my-staff
|
||
|
Parent: =>Page.about
|
||
|
staffduplicate:
|
||
|
Title: Staff
|
||
|
URLSegment: my-staff
|
||
|
Parent: =>Page.about
|
||
|
products:
|
||
|
Title: Products
|
||
|
product1:
|
||
|
Title: 1.1 Test Product
|
||
|
product2:
|
||
|
Title: Another Product
|
||
|
product3:
|
||
|
Title: Another Product
|
||
|
product4:
|
||
|
Title: Another Product
|
||
|
contact:
|
||
|
Title: Contact Us
|
||
|
|
||
|
ErrorPage:
|
||
|
404:
|
||
|
Title: Page not Found
|
||
|
ErrorCode: 404
|
||
|
|
||
|
|
||
|
The contents of the YAML file are broken into three levels.
|
||
|
|
||
|
* **Top level: class names** - Page and ErrorPage. This is the name of the dataobject class that should be created.
|
||
|
The fact that ErrorPage is actually a subclass is irrelevant to the system populating the database. It just
|
||
|
instantiates the object you specify.
|
||
|
* **Second level: identifiers** - home, about, staff, staffduplicate, etc. These are the identifiers that you pass as
|
||
|
the second argument of SapphireTest::objFromFixture(). Each identifier you specify delimits a new database record.
|
||
|
This means that every record needs to have an identifier, whether you use it or not.
|
||
|
* **Third level: fields** - each field for the record is listed as a 3rd level entry. In most cases, the field's raw
|
||
|
content is provided. However, if you want to define a relationship, you can do so using "=>".
|
||
|
|
||
|
There are a couple of lines like this:
|
||
|
|
||
|
Parent: =>Page.about
|
||
|
|
||
|
This will tell the system to set the ParentID database field to the ID of the Page object with the identifier "about".
|
||
|
This can be used on any has-one or many-many relationship. Note that we use the name of the relationship (Parent), and
|
||
|
not the name of the database field (ParentID)
|
||
|
|
||
|
On many-many relationships, you should specify a comma separated list of values.
|
||
|
|
||
|
MyRelation: =>Class.inst1,=>Class.inst2,=>Class.inst3
|
||
|
|
||
|
An crucial thing to note is that **the YAML file specifies DataObjects, not database records**. The database is
|
||
|
populated by instantiating DataObject objects, setting the fields listed, and calling write(). This means that any
|
||
|
onBeforeWrite() or default value logic will be executed as part of the test. This forms the basis of our
|
||
|
testURLGeneration() test above.
|
||
|
|
||
|
For example, the URLSegment value of Page.staffduplicate is the same as the URLSegment value of Page.staff. When the
|
||
|
fixture is set up, the URLSegment value of Page.staffduplicate will actually be my-staff-2.
|
||
|
|
||
|
Finally, be aware that requireDefaultRecords() is **not** called by the database populator - so you will need to specify
|
||
|
standard pages such as 404 and home in your YAML file.
|