mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Squashing previous corrections into one commit along with a couple more
corrections to the docs, including changing the example seen in fixtures.md
This commit is contained in:
parent
9fa8945f2a
commit
3e5f788ddc
@ -15,23 +15,19 @@ The simple usage, Permission::check("PERM_CODE") will detect if the currently lo
|
||||
**Group ACLs**
|
||||
|
||||
* Call **Permission::check("MY_PERMISSION_CODE")** to see if the current user has MY_PERMISSION_CODE.
|
||||
* MY_PERMISSION_CODE can be loaded into the Security admin on the appropriate group, using the "Permissions" tab.
|
||||
|
||||
You can use whatever codes you like, but for the sanity of developers and users, it would be worth listing the codes in
|
||||
[permissions:codes](/reference/permission)
|
||||
* MY_PERMISSION_CODE can be loaded into the Security admin on the appropriate group, using the "Permissions" tab.
|
||||
|
||||
## PermissionProvider
|
||||
|
||||
`[api:PermissionProvider]` is an interface which lets you define a method *providePermissions()*. This method should return a
|
||||
map of permission code names with a human readable explanation of its purpose (see
|
||||
[permissions:codes](/reference/permission)).
|
||||
`[api:PermissionProvider]` is an interface which lets you define a method *providePermissions()*.
|
||||
This method should return a map of permission code names with a human readable explanation of its purpose.
|
||||
|
||||
:::php
|
||||
class Page_Controller implements PermissionProvider {
|
||||
public function init() {
|
||||
if(!Permission::check("VIEW_SITE")) Security::permissionFailure();
|
||||
}
|
||||
|
||||
|
||||
public function providePermissions() {
|
||||
return array(
|
||||
"VIEW_SITE" => "Access the site",
|
||||
@ -53,7 +49,7 @@ By default, permissions are used in the following way:
|
||||
* If not logged in, the 'View' permissions must be 'anyone logged in' for a page to be displayed in a menu
|
||||
* If logged in, you must be allowed to view a page for it to be displayed in a menu
|
||||
|
||||
**NOTE:** Should the canView() method on SiteTree be updated to call Permission::check("SITETREE_VIEW", $this->ID)?
|
||||
**NOTE:** Should the canView() method on SiteTree be updated to call Permission::check("SITETREE_VIEW", $this->ID)?
|
||||
Making this work well is a subtle business and should be discussed with a few developers.
|
||||
|
||||
## Setting up permissions
|
||||
|
@ -1,9 +1,11 @@
|
||||
# Creating a SilverStripe Test
|
||||
|
||||
A test is created by extending one of two classes, SapphireTest and FunctionalTest. You would subclass SapphireTest to
|
||||
test your application logic, for example testing the behaviour of one of your `[api:DataObjects]`, whereas FunctionalTest
|
||||
is extended when you want to test your application's functionality, such as testing the results of GET and POST requests,
|
||||
and validating the content of a page. `[api:FunctionalTest]` is a subclass of `[api:SapphireTest]`.
|
||||
A test is created by extending one of two classes, SapphireTest and FunctionalTest.
|
||||
You would subclass `[api:SapphireTest]` to test your application logic,
|
||||
for example testing the behaviour of one of your `[DataObjects](api:DataObject)`,
|
||||
whereas `[api:FunctionalTest]` is extended when you want to test your application's functionality,
|
||||
such as testing the results of GET and POST requests,
|
||||
and validating the content of a page. FunctionalTest is a subclass of SapphireTest.
|
||||
|
||||
## Creating a test from SapphireTest
|
||||
|
||||
@ -27,11 +29,7 @@ Here is an example of a test which extends SapphireTest:
|
||||
'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',
|
||||
'staffduplicate' => 'my-staff-2'
|
||||
);
|
||||
|
||||
foreach($expectedURLs as $fixture => $urlSegment) {
|
||||
@ -44,11 +42,11 @@ Here is an example of a test which extends SapphireTest:
|
||||
Firstly we define a static member `$fixture_file`, this should point to a file that represents the data we want to test,
|
||||
represented in YAML. When our test is run, the data from this file will be loaded into a test database for our test to use.
|
||||
This property can be an array of strings pointing to many .yml files, but for our test we are just using a string on its
|
||||
own. For more detail on fixtures, see the [page on fixtures](fixtures).
|
||||
own. For more detail on fixtures, see [this page](fixtures).
|
||||
|
||||
The second part of our class is the `testURLGeneration` method. This method is our test. You can asign many tests, but
|
||||
again for our purposes there is just the one. When the test is executed, methods prefixed with the word **test** will be
|
||||
run. The test database is rebuilt everytime one of these methods is run.
|
||||
again for our purposes there is just the one. When the test is executed, methods prefixed with the word `test` will be
|
||||
run. The test database is rebuilt every time one of these methods is run.
|
||||
|
||||
Inside our test method is the `objFromFixture` method that will generate an object for us based on data from our fixture
|
||||
file. To identify to the object, we provide a class name and an identifier. The identifier is specified in the YAML file
|
||||
@ -57,10 +55,10 @@ database. This means that you can use it to test the functions responsible for l
|
||||
|
||||
The final part of our test is an assertion command, `assertEquals`. An assertion command allows us to test for something
|
||||
in our test methods (in this case we are testing if two values are equal). A test method can have more than one assertion
|
||||
command, and if anyone of these tests fail, then the whole test method will fail.
|
||||
command, and if any one of these assertions fail, so will the test method.
|
||||
|
||||
For more information on PHPUnit's assertions see the [PHPUnit manual](http://www.phpunit.de/manual/current/en/api.html#api.assert).
|
||||
|
||||
The `[api:SapphireTest]` class comes with additional assertions which are more specific to the Sapphire, for example the
|
||||
`[assertEmailSent](api:SapphireTest->assertEmailSent())` method, which simulates sending emails through the `Email->send()`
|
||||
API without actually using a mail server. For more details on this see th [testing emails](testing-email)) guide.
|
||||
The `[api:SapphireTest]` class comes with additional assertions which are more specific to Sapphire, for example the
|
||||
`assertEmailSent` method, which simulates sending emails through the `Email->send()`
|
||||
API without actually using a mail server. For more details on this see the [testing emails](testing-email) guide.
|
||||
|
@ -2,177 +2,223 @@
|
||||
|
||||
## Overview
|
||||
|
||||
Often you need to test your functionality with some existing data, so called "fixtures".
|
||||
The `[api:SapphireTest]` class already prepares an empty database for you,
|
||||
and you have various ways to define those fixtures.
|
||||
You will often find the need to test your functionality with some consistent data.
|
||||
If we are testing our code with the same data each time,
|
||||
we can trust our tests to yeild reliable results.
|
||||
In Silverstripe we define this data via 'fixtures' (so called because of their fixed nature).
|
||||
The `[api:SapphireTest]` class takes care of populating a test database with data from these fixtures -
|
||||
all we have to do is define them, and we have a few ways in which we can do this.
|
||||
|
||||
## YAML Fixtures
|
||||
|
||||
YAML is a markup language which is deliberately simple and easy to read,
|
||||
so ideal for our fixture generation.
|
||||
so it is ideal for fixture generation.
|
||||
|
||||
We will begin with a sample file and talk our way through it.
|
||||
Say we have the following two DataObjects:
|
||||
|
||||
Page:
|
||||
home:
|
||||
Title: Home
|
||||
about:
|
||||
Title: About Us
|
||||
staff:
|
||||
Title: Staff
|
||||
URLSegment: my-staff
|
||||
Parent: =>Page.about
|
||||
|
||||
RedirectorPage:
|
||||
redirect_home:
|
||||
RedirectionType: Internal
|
||||
LinkTo: =>Page.home
|
||||
:::php
|
||||
class Player extends DataObject {
|
||||
static $db = array (
|
||||
'Name' => 'Varchar(255)'
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
'Team' => 'Team'
|
||||
);
|
||||
}
|
||||
|
||||
The contents of the YAML file are broken into three levels.
|
||||
class Team extends DataObject {
|
||||
static $db = array (
|
||||
'Name' => 'Varchar(255)',
|
||||
'Origin' => 'Varchar(255)'
|
||||
);
|
||||
|
||||
* **Top level: class names** - `Page` and `RedirectorPage`. This is the name of the dataobject class that should be created.
|
||||
The fact that `RedirectorPage` is actually a subclass is irrelevant to the system populating the database. It just
|
||||
instantiates the object you specify.
|
||||
* **Second level: identifiers** - `home`, `about`, 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 "=>".
|
||||
static $has_many = array(
|
||||
'Players' => 'Player'
|
||||
);
|
||||
}
|
||||
|
||||
There are a couple of lines like this:
|
||||
We can represent multiple instances of them in `YAML` as follows:
|
||||
|
||||
Parent: =>Page.about
|
||||
:::yml
|
||||
Player:
|
||||
john:
|
||||
Name: John
|
||||
Team: =>Team.hurricanes
|
||||
joe:
|
||||
Name: Joe
|
||||
Team: =>Team.crusaders
|
||||
jack:
|
||||
Name: Jack
|
||||
Team: =>Team.crusaders
|
||||
Team:
|
||||
hurricanes:
|
||||
Name: The Hurricanes
|
||||
Origin: Wellington
|
||||
crusaders:
|
||||
Name: The Crusaders
|
||||
Origin: Bay of Plenty
|
||||
|
||||
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)
|
||||
Our `YAML` is broken up into three levels, signified by the indentation of each line.
|
||||
In the first level of indentation, `Player` and `Team`,
|
||||
represent the class names of the objects we want to be created for the test.
|
||||
|
||||
On many-many relationships, you should specify a comma separated list of values.
|
||||
The second level, `john`/`joe`/`jack` & `hurricanes`/`crusaders`, are identifiers.
|
||||
These are what you pass as the second argument of `SapphireTest::objFromFixture()`.
|
||||
Each identifier you specify represents a new object.
|
||||
|
||||
MyRelation: =>Class.inst1,=>Class.inst2,=>Class.inst3
|
||||
The third and final level represents each individual object's fields.
|
||||
A field can either be provided with raw data (such as the Names for our Players),
|
||||
or we can define a relationship, as seen by the fields prefixed with `=>`.
|
||||
|
||||
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.
|
||||
Each one of our Players has a relationship to a Team,
|
||||
this is shown with the `Team` field for each `Player` being set to `=>Team.` followed by a team name.
|
||||
Take the player John for example, his team is the Hurricanes which is represented by `=>Team.hurricanes`.
|
||||
This is tells the system that we want to set up a relationship for the `Player` object `john` with the `Team` object `hurricanes`.
|
||||
It will populate the `Player` object's `TeamID` with the ID of `hurricanes`,
|
||||
just like how a relationship is always set up.
|
||||
|
||||
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.
|
||||
<div class="hint" markdown='1'>
|
||||
Note that we use the name of the relationship (Team), and not the name of the database field (TeamID).
|
||||
</div>
|
||||
|
||||
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.
|
||||
This style of relationship declaration can be used for both a `has-one` and a `many-many` relationship.
|
||||
For `many-many` relationships, we specify a comma separated list of values.
|
||||
For example we could just as easily write the above as:
|
||||
|
||||
:::yml
|
||||
Player:
|
||||
john:
|
||||
Name: John
|
||||
joe:
|
||||
Name: Joe
|
||||
jack:
|
||||
Name: Jack
|
||||
Team:
|
||||
hurricanes:
|
||||
Name: The Hurricanes
|
||||
Origin: Wellington
|
||||
Players: =>Player.john
|
||||
crusaders:
|
||||
Name: The Crusaders
|
||||
Origin: Bay of Plenty
|
||||
Players: =>Player.joe,=>Player.jack
|
||||
|
||||
A crucial thing to note is that **the YAML file specifies DataObjects, not database records**.
|
||||
The database is populated by instantiating DataObject objects and setting the fields declared in the YML,
|
||||
then calling write() on those objects.
|
||||
This means that any `onBeforeWrite()` or default value logic will be executed as part of the test.
|
||||
The reasoning behind this is to allow us to test the `onBeforeWrite` functionality of our objects.
|
||||
You can see this kind of testing in action in the `testURLGeneration()` test from the example in
|
||||
[Creating a SilverStripe Test](creating-a-silverstripe-test).
|
||||
|
||||
## Test Class Definition
|
||||
|
||||
### Manual Object Creation
|
||||
|
||||
|
||||
## Manual Object Creation
|
||||
|
||||
Sometimes statically defined fixtures don't suffice, because of the complexity of the tested model,
|
||||
or because the YAML format doesn't allow you to modify all model state.
|
||||
Sometimes statically defined fixtures don't suffice. This could be because of the complexity of the tested model,
|
||||
or because the YAML format doesn't allow you to modify all of a model's state.
|
||||
One common example here is publishing pages (page fixtures aren't published by default).
|
||||
|
||||
You can always resort to creating objects manually in the test setup phase.
|
||||
Since the test database is cleared on every test method, you'll get a fresh
|
||||
set of test instances every time.
|
||||
Since the test database is cleared on every test method, you'll get a fresh set of test instances every time.
|
||||
|
||||
:::php
|
||||
class SiteTreeTest extends SapphireTest {
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
:::php
|
||||
class SiteTreeTest extends SapphireTest {
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
for($i=0; $i<100; $i++) {
|
||||
$page = new Page(array('Title' => "Page $i"));
|
||||
$page->write();
|
||||
$page->publish('Stage', 'Live');
|
||||
}
|
||||
}
|
||||
}
|
||||
for($i=0; $i<100; $i++) {
|
||||
$page = new Page(array('Title' => "Page $i"));
|
||||
$page->write();
|
||||
$page->publish('Stage', 'Live');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Fixture Factories
|
||||
|
||||
### Why Factories?
|
||||
|
||||
Manually defined fixture provide full flexibility, but very little in terms of structure and convention.
|
||||
Alternatively, you can use the `[api:FixtureFactory]` class, which allows you
|
||||
to set default values, callbacks on object creation, and dynamic/lazy value setting.
|
||||
By the way, the `SapphireTest` YAML fixtures rely on internally on this class as well.
|
||||
While manually defined fixtures provide full flexibility, they offer very little in terms of structure and convention.
|
||||
Alternatively, you can use the `[api:FixtureFactory]` class, which allows you to set default values,
|
||||
callbacks on object creation, and dynamic/lazy value setting.
|
||||
|
||||
The idea is that rather than instanciating objects directly, we'll have a factory class for them.
|
||||
This factory can have so called "blueprints" defined on it, which tells the factory
|
||||
how to instanciate an object of a specific type. Blueprints need a name,
|
||||
which is usually set to the class it creates.
|
||||
<div class="hint" markdown='1'>
|
||||
SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
|
||||
</div>
|
||||
|
||||
The idea is that rather than instantiating objects directly, we'll have a factory class for them.
|
||||
This factory can have so called "blueprints" defined on it, which tells the factory how to instantiate an object of a specific type. Blueprints need a name, which is usually set to the class it creates.
|
||||
|
||||
### Usage
|
||||
|
||||
Since blueprints are auto-created for all available DataObject subclasses,
|
||||
you only need to instanciate a factory to start using it.
|
||||
you only need to instantiate a factory to start using it.
|
||||
|
||||
:::php
|
||||
$factory = Injector::inst()->create('FixtureFactory');
|
||||
$obj = $factory->createObject('MyClass', 'myobj1');
|
||||
:::php
|
||||
$factory = Injector::inst()->create('FixtureFactory');
|
||||
$obj = $factory->createObject('MyClass', 'myobj1');
|
||||
|
||||
It is important to remember that fixtures are referenced by arbitrary
|
||||
identifiers ('myobj1'). These are internally mapped to their database identifiers.
|
||||
|
||||
:::
|
||||
$databaseId = $factory->getId('MyClass', 'myobj1');
|
||||
:::
|
||||
$databaseId = $factory->getId('MyClass', 'myobj1');
|
||||
|
||||
In order to create an object with certain properties, just add a second argument:
|
||||
|
||||
:::php
|
||||
$obj = $factory->createObject('MyClass', 'myobj1', array('MyProperty' => 'My Value'));
|
||||
:::php
|
||||
$obj = $factory->createObject('MyClass', 'myobj1', array('MyProperty' => 'My Value'));
|
||||
|
||||
### Default Properties
|
||||
#### Default Properties
|
||||
|
||||
Blueprints can be overwritten in order to customize their behaviour,
|
||||
for example with default properties in case none are passed into `createObject()`.
|
||||
|
||||
:::php
|
||||
$factory->define('MyObject', array(
|
||||
'MyProperty' => 'My Default Value'
|
||||
));
|
||||
:::php
|
||||
$factory->define('MyObject', array(
|
||||
'MyProperty' => 'My Default Value'
|
||||
));
|
||||
|
||||
### Dependent Properties
|
||||
#### Dependent Properties
|
||||
|
||||
Values can be set on demand through anonymous functions,
|
||||
which can either generate random defaults, or create
|
||||
composite values based on other fixture data.
|
||||
Values can be set on demand through anonymous functions, which can either generate random defaults,
|
||||
or create composite values based on other fixture data.
|
||||
|
||||
:::php
|
||||
$factory->define('Member', array(
|
||||
'Email' => function($obj, $data, $fixtures) {
|
||||
if(isset($data['FirstName']) {
|
||||
$obj->Email = strtolower($data['FirstName']) . '@example.org';
|
||||
}
|
||||
},
|
||||
'Score' => function($obj, $data, $fixtures) {
|
||||
$obj->Score = rand(0,10);
|
||||
}
|
||||
));
|
||||
:::php
|
||||
$factory->define('Member', array(
|
||||
'Email' => function($obj, $data, $fixtures) {
|
||||
if(isset($data['FirstName']) {
|
||||
$obj->Email = strtolower($data['FirstName']) . '@example.org';
|
||||
}
|
||||
},
|
||||
'Score' => function($obj, $data, $fixtures) {
|
||||
$obj->Score = rand(0,10);
|
||||
}
|
||||
));
|
||||
|
||||
### Relations
|
||||
#### Relations
|
||||
|
||||
Model relations can be expressed through the same notation as in the YAML fixture format
|
||||
described earlier, through the `=>` prefix on data values.
|
||||
|
||||
:::php
|
||||
$obj = $factory->createObject('MyObject', 'myobj1', array(
|
||||
'MyHasManyRelation' => '=>MyOtherObject.obj1,=>MyOtherObject.obj2'
|
||||
));
|
||||
:::php
|
||||
$obj = $factory->createObject('MyObject', 'myobj1', array(
|
||||
'MyHasManyRelation' => '=>MyOtherObject.obj1,=>MyOtherObject.obj2'
|
||||
));
|
||||
|
||||
### Callbacks
|
||||
#### Callbacks
|
||||
|
||||
Sometimes new model instances need to be modified in ways which can't be expressed
|
||||
in their properties, for example to publish a page, which requires a method call.
|
||||
|
||||
:::php
|
||||
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
|
||||
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
|
||||
$obj->publish('Stage', 'Live');
|
||||
});
|
||||
$page = $factory->define('Page', $blueprint);
|
||||
:::php
|
||||
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
|
||||
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
|
||||
$obj->publish('Stage', 'Live');
|
||||
});
|
||||
$page = $factory->define('Page', $blueprint);
|
||||
|
||||
Available callbacks:
|
||||
|
||||
@ -186,43 +232,43 @@ CMS admins could both inherit from the `Member` class, but have completely
|
||||
different properties. This is where named blueprints come in.
|
||||
By default, blueprint names equal the class names they manage.
|
||||
|
||||
:::php
|
||||
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
|
||||
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
|
||||
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
|
||||
if(isset($fixtures['Group']['admin'])) {
|
||||
$adminGroup = Group::get()->byId($fixtures['Group']['admin']);
|
||||
$obj->Groups()->add($adminGroup);
|
||||
}
|
||||
});
|
||||
|
||||
$member = $factory->createObject('Member'); // not in admin group
|
||||
$admin = $factory->createObject('AdminMember'); // in admin group
|
||||
:::php
|
||||
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
|
||||
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
|
||||
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
|
||||
if(isset($fixtures['Group']['admin'])) {
|
||||
$adminGroup = Group::get()->byId($fixtures['Group']['admin']);
|
||||
$obj->Groups()->add($adminGroup);
|
||||
}
|
||||
});
|
||||
|
||||
$member = $factory->createObject('Member'); // not in admin group
|
||||
$admin = $factory->createObject('AdminMember'); // in admin group
|
||||
|
||||
### Full Test Example
|
||||
|
||||
:::php
|
||||
class MyObjectTest extends SapphireTest {
|
||||
:::php
|
||||
class MyObjectTest extends SapphireTest {
|
||||
|
||||
protected $factory;
|
||||
protected $factory;
|
||||
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$factory = Injector::inst()->create('FixtureFactory');
|
||||
// Defines a "blueprint" for new objects
|
||||
$factory->define('MyObject', array(
|
||||
'MyProperty' => 'My Default Value'
|
||||
));
|
||||
$this->factory = $factory;
|
||||
}
|
||||
$factory = Injector::inst()->create('FixtureFactory');
|
||||
// Defines a "blueprint" for new objects
|
||||
$factory->define('MyObject', array(
|
||||
'MyProperty' => 'My Default Value'
|
||||
));
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
function testSomething() {
|
||||
$MyObjectObj = $this->factory->createObject(
|
||||
'MyObject',
|
||||
array('MyOtherProperty' => 'My Custom Value')
|
||||
);
|
||||
// $myPageObj->MyProperty = My Default Value
|
||||
// $myPageObj->MyOtherProperty = My Custom Value
|
||||
}
|
||||
}
|
||||
function testSomething() {
|
||||
$MyObjectObj = $this->factory->createObject(
|
||||
'MyObject',
|
||||
array('MyOtherProperty' => 'My Custom Value')
|
||||
);
|
||||
// $myPageObj->MyProperty = My Default Value
|
||||
// $myPageObj->MyOtherProperty = My Custom Value
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ test.
|
||||
**Fake Object**: A substitute object that simply replaces a real object with the same interface, and returns a
|
||||
pre-determined (usually fixed) value from each method.
|
||||
|
||||
**Mock Object:** A substitute object that mimicks the same behavior as a real object (some people think of mocks as
|
||||
**Mock Object:** A substitute object that mimics the same behavior as a real object (some people think of mocks as
|
||||
"crash test dummy" objects). Mocks differ from other kinds of substitute objects in that they must understand the
|
||||
context of each call to them, setting expectations of which, and what order, methods will be invoked and what parameters
|
||||
will be passed.
|
||||
|
@ -111,7 +111,7 @@ All command-line arguments are documented on
|
||||
### Via the "sake" Wrapper on Command Line
|
||||
|
||||
The [sake](/topics/commandline) executable that comes with SilverStripe can trigger a customized
|
||||
"[api:TestRunner]" class that handles the PHPUnit configuration and output formatting.
|
||||
`[api:TestRunner]` class that handles the PHPUnit configuration and output formatting.
|
||||
While the custom test runner a handy tool, its also more limited than using `phpunit` directly,
|
||||
particularly around formatting test output.
|
||||
|
||||
|
@ -1,48 +1,46 @@
|
||||
# Testing Email
|
||||
|
||||
SilverStripe's test system has built-in support for testing emails sent using the Email class.
|
||||
SilverStripe's test system has built-in support for testing emails sent using the `[api:Email]` class.
|
||||
|
||||
## How it works
|
||||
|
||||
For this to work, you need to send emails using the `Email` class, which is generally the way that we recommend you
|
||||
send emails in your SilverStripe application. Here is a simple example of how you might do this:
|
||||
For this to work, you need to send emails using the `Email` class,
|
||||
which is generally the way that we recommend you send emails in your SilverStripe application.
|
||||
Here is a simple example of how you might do this:
|
||||
|
||||
:::php
|
||||
$e = new Email();
|
||||
$e->To = "someone@example.com";
|
||||
$e->Subject = "Hi there";
|
||||
$e->Body = "I just really wanted to email you and say hi.";
|
||||
$e->send();
|
||||
:::php
|
||||
$e = new Email();
|
||||
$e->To = "someone@example.com";
|
||||
$e->Subject = "Hi there";
|
||||
$e->Body = "I just really wanted to email you and say hi.";
|
||||
$e->send();
|
||||
|
||||
Normally, the `send()` method would send an email using PHP's `mail()` function.
|
||||
However, if you are running a `[api:SapphireTest]` test, then it holds off actually sending the email,
|
||||
and instead lets you assert that an email was sent using this method.
|
||||
|
||||
Normally, the send() method would send an email using PHP's mail() function. However, if you are running a `[api:SapphireTest]`
|
||||
test, then it holds off actually sending the email, and instead lets you assert that an email was sent using this method.
|
||||
:::php
|
||||
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
|
||||
|
||||
:::php
|
||||
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
|
||||
The arguments are `$to`, `$from`, `$subject`, `$body`, and can take one of the three following types:
|
||||
|
||||
|
||||
The arguments are `$to`, `$from`, `$subject`, `$body`, and can be take one of the following three forms:
|
||||
|
||||
* A string: match exactly that string
|
||||
* `null/false`: match anything
|
||||
* A PERL regular expression (starting with '/'): match that regular expression
|
||||
* A string: match exactly that string
|
||||
* `null/false`: match anything
|
||||
* A PERL regular expression (starting with '/'): match that regular expression
|
||||
|
||||
## How to use it
|
||||
|
||||
Given all of that, there is not a lot that you have to do in order to test emailing functionality in your application.
|
||||
|
||||
* Write your SilverStripe application, using the Email class to send emails.
|
||||
* Write tests that trigger the email sending functionality.
|
||||
* Include appropriate `$this->assertEmailSent()` calls in those tests.
|
||||
Whenever we include e-mailing functionality in our application,
|
||||
we simply use `$this->assertEmailSent()` to check our mail has been passed to PHP `mail` in our tests.
|
||||
|
||||
That's it!
|
||||
|
||||
## What isn't tested
|
||||
|
||||
It's important to realise that this email testing doesn't actually test everything that there is to do with email. The
|
||||
focus of this email testing system is testing that your application is triggering emails correctly. It doesn't test
|
||||
your email infrastructure outside of the webserver. For example:
|
||||
It's important to realise that this email testing doesn't actually test everything that there is to do with email.
|
||||
The focus of this email testing system is testing that your application is triggering emails correctly.
|
||||
It doesn't test your email infrastructure outside of the webserver. For example:
|
||||
|
||||
* It won't test that email is correctly configured on your webserver
|
||||
* It won't test whether your emails are going to be lost in someone's spam filter
|
||||
@ -50,9 +48,9 @@ your email infrastructure outside of the webserver. For example:
|
||||
|
||||
## How it's built
|
||||
|
||||
For those of you who want to dig a little deeper, here's a quick run-through of how the system has been built. As well
|
||||
as explaining how we built the email test, this is a good design pattern for making other "tricky external systems"
|
||||
testable:
|
||||
For those of you who want to dig a little deeper, here's a quick run-through of how the system has been built.
|
||||
As well as explaining how we built the email test,
|
||||
this is a good design pattern for making other "tricky external systems" testable:
|
||||
|
||||
1. The `Email::send()` method makes uses of a static object, `Email::$mailer`, to do the dirty work of calling
|
||||
mail(). The default mailer is an object of type `Mailer`, which performs a normal send.
|
||||
|
Loading…
Reference in New Issue
Block a user