Merge pull request #2443 from dangerdan/doc_corrections

Documentation corrections
This commit is contained in:
Ingo Schommer 2013-09-24 11:45:53 -07:00
commit 3edbfa5b99
6 changed files with 236 additions and 198 deletions

View File

@ -15,23 +15,19 @@ The simple usage, Permission::check("PERM_CODE") will detect if the currently lo
**Group ACLs** **Group ACLs**
* Call **Permission::check("MY_PERMISSION_CODE")** to see if the current user has MY_PERMISSION_CODE. * 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. * 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)
## PermissionProvider ## PermissionProvider
`[api:PermissionProvider]` is an interface which lets you define a method *providePermissions()*. This method should return a `[api:PermissionProvider]` is an interface which lets you define a method *providePermissions()*.
map of permission code names with a human readable explanation of its purpose (see This method should return a map of permission code names with a human readable explanation of its purpose.
[permissions:codes](/reference/permission)).
:::php :::php
class Page_Controller implements PermissionProvider { class Page_Controller implements PermissionProvider {
public function init() { public function init() {
if(!Permission::check("VIEW_SITE")) Security::permissionFailure(); if(!Permission::check("VIEW_SITE")) Security::permissionFailure();
} }
public function providePermissions() { public function providePermissions() {
return array( return array(
"VIEW_SITE" => "Access the site", "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 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 * 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. Making this work well is a subtle business and should be discussed with a few developers.
## Setting up permissions ## Setting up permissions

View File

@ -1,9 +1,11 @@
# Creating a SilverStripe Test # Creating a SilverStripe Test
A test is created by extending one of two classes, SapphireTest and FunctionalTest. You would subclass SapphireTest to A test is created by extending one of two classes, SapphireTest and FunctionalTest.
test your application logic, for example testing the behaviour of one of your `[api:DataObjects]`, whereas FunctionalTest You would subclass `[api:SapphireTest]` to test your application logic,
is extended when you want to test your application's functionality, such as testing the results of GET and POST requests, for example testing the behaviour of one of your `[DataObjects](api:DataObject)`,
and validating the content of a page. `[api:FunctionalTest]` is a subclass of `[api:SapphireTest]`. 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 ## Creating a test from SapphireTest
@ -27,11 +29,7 @@ Here is an example of a test which extends SapphireTest:
'home' => 'home', 'home' => 'home',
'staff' => 'my-staff', 'staff' => 'my-staff',
'about' => 'about-us', 'about' => 'about-us',
'staffduplicate' => 'my-staff-2', '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) { 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, 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. 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 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 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 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. 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 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 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 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 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). 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 The `[api:SapphireTest]` class comes with additional assertions which are more specific to Sapphire, for example the
`[assertEmailSent](api:SapphireTest->assertEmailSent())` method, which simulates sending emails through the `Email->send()` `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. API without actually using a mail server. For more details on this see the [testing emails](testing-email) guide.

View File

@ -2,177 +2,223 @@
## Overview ## Overview
Often you need to test your functionality with some existing data, so called "fixtures". You will often find the need to test your functionality with some consistent data.
The `[api:SapphireTest]` class already prepares an empty database for you, If we are testing our code with the same data each time,
and you have various ways to define those fixtures. 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 Fixtures
YAML is a markup language which is deliberately simple and easy to read, 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: :::php
home: class Player extends DataObject {
Title: Home static $db = array (
about: 'Name' => 'Varchar(255)'
Title: About Us );
staff:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
RedirectorPage:
redirect_home:
RedirectionType: Internal
LinkTo: =>Page.home
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. static $has_many = array(
The fact that `RedirectorPage` is actually a subclass is irrelevant to the system populating the database. It just 'Players' => 'Player'
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 "=>".
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". Our `YAML` is broken up into three levels, signified by the indentation of each line.
This can be used on any has-one or many-many relationship. Note that we use the name of the relationship (Parent), and In the first level of indentation, `Player` and `Team`,
not the name of the database field (ParentID) 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 Each one of our Players has a relationship to a Team,
populated by instantiating DataObject objects, setting the fields listed, and calling write(). This means that any this is shown with the `Team` field for each `Player` being set to `=>Team.` followed by a team name.
onBeforeWrite() or default value logic will be executed as part of the test. This forms the basis of our Take the player John for example, his team is the Hurricanes which is represented by `=>Team.hurricanes`.
testURLGeneration() test above. 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 <div class="hint" markdown='1'>
fixture is set up, the URLSegment value of Page.staffduplicate will actually be my-staff-2. 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 This style of relationship declaration can be used for both a `has-one` and a `many-many` relationship.
standard pages such as 404 and home in your YAML file. 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 ## Test Class Definition
### Manual Object Creation
Sometimes statically defined fixtures don't suffice. This could be because of the complexity of the tested model,
## Manual Object Creation or because the YAML format doesn't allow you to modify all of a model's state.
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.
One common example here is publishing pages (page fixtures aren't published by default). 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. 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 Since the test database is cleared on every test method, you'll get a fresh set of test instances every time.
set of test instances every time.
:::php :::php
class SiteTreeTest extends SapphireTest { class SiteTreeTest extends SapphireTest {
function setUp() { function setUp() {
parent::setUp(); parent::setUp();
for($i=0; $i<100; $i++) { for($i=0; $i<100; $i++) {
$page = new Page(array('Title' => "Page $i")); $page = new Page(array('Title' => "Page $i"));
$page->write(); $page->write();
$page->publish('Stage', 'Live'); $page->publish('Stage', 'Live');
} }
} }
} }
## Fixture Factories ## Fixture Factories
### Why Factories? ### Why Factories?
Manually defined fixture provide full flexibility, but very little in terms of structure and convention. 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 Alternatively, you can use the `[api:FixtureFactory]` class, which allows you to set default values,
to set default values, callbacks on object creation, and dynamic/lazy value setting. callbacks on object creation, and dynamic/lazy value setting.
By the way, the `SapphireTest` YAML fixtures rely on internally on this class as well.
The idea is that rather than instanciating objects directly, we'll have a factory class for them. <div class="hint" markdown='1'>
This factory can have so called "blueprints" defined on it, which tells the factory SapphireTest uses FixtureFactory under the hood when it is provided with YAML based fixtures.
how to instanciate an object of a specific type. Blueprints need a name, </div>
which is usually set to the class it creates.
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 ### Usage
Since blueprints are auto-created for all available DataObject subclasses, 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 :::php
$factory = Injector::inst()->create('FixtureFactory'); $factory = Injector::inst()->create('FixtureFactory');
$obj = $factory->createObject('MyClass', 'myobj1'); $obj = $factory->createObject('MyClass', 'myobj1');
It is important to remember that fixtures are referenced by arbitrary It is important to remember that fixtures are referenced by arbitrary
identifiers ('myobj1'). These are internally mapped to their database identifiers. 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: In order to create an object with certain properties, just add a second argument:
:::php :::php
$obj = $factory->createObject('MyClass', 'myobj1', array('MyProperty' => 'My Value')); $obj = $factory->createObject('MyClass', 'myobj1', array('MyProperty' => 'My Value'));
### Default Properties #### Default Properties
Blueprints can be overwritten in order to customize their behaviour, Blueprints can be overwritten in order to customize their behaviour,
for example with default properties in case none are passed into `createObject()`. for example with default properties in case none are passed into `createObject()`.
:::php :::php
$factory->define('MyObject', array( $factory->define('MyObject', array(
'MyProperty' => 'My Default Value' 'MyProperty' => 'My Default Value'
)); ));
### Dependent Properties #### Dependent Properties
Values can be set on demand through anonymous functions, Values can be set on demand through anonymous functions, which can either generate random defaults,
which can either generate random defaults, or create or create composite values based on other fixture data.
composite values based on other fixture data.
:::php :::php
$factory->define('Member', array( $factory->define('Member', array(
'Email' => function($obj, $data, $fixtures) { 'Email' => function($obj, $data, $fixtures) {
if(isset($data['FirstName']) { if(isset($data['FirstName']) {
$obj->Email = strtolower($data['FirstName']) . '@example.org'; $obj->Email = strtolower($data['FirstName']) . '@example.org';
} }
}, },
'Score' => function($obj, $data, $fixtures) { 'Score' => function($obj, $data, $fixtures) {
$obj->Score = rand(0,10); $obj->Score = rand(0,10);
} }
)); ));
### Relations #### Relations
Model relations can be expressed through the same notation as in the YAML fixture format Model relations can be expressed through the same notation as in the YAML fixture format
described earlier, through the `=>` prefix on data values. described earlier, through the `=>` prefix on data values.
:::php :::php
$obj = $factory->createObject('MyObject', 'myobj1', array( $obj = $factory->createObject('MyObject', 'myobj1', array(
'MyHasManyRelation' => '=>MyOtherObject.obj1,=>MyOtherObject.obj2' 'MyHasManyRelation' => '=>MyOtherObject.obj1,=>MyOtherObject.obj2'
)); ));
### Callbacks #### Callbacks
Sometimes new model instances need to be modified in ways which can't be expressed 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. in their properties, for example to publish a page, which requires a method call.
:::php :::php
$blueprint = Injector::inst()->create('FixtureBlueprint', 'Member'); $blueprint = Injector::inst()->create('FixtureBlueprint', 'Member');
$blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) { $blueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
$obj->publish('Stage', 'Live'); $obj->publish('Stage', 'Live');
}); });
$page = $factory->define('Page', $blueprint); $page = $factory->define('Page', $blueprint);
Available callbacks: 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. different properties. This is where named blueprints come in.
By default, blueprint names equal the class names they manage. By default, blueprint names equal the class names they manage.
:::php :::php
$memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member'); $memberBlueprint = Injector::inst()->create('FixtureBlueprint', 'Member', 'Member');
$adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member'); $adminBlueprint = Injector::inst()->create('FixtureBlueprint', 'AdminMember', 'Member');
$adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) { $adminBlueprint->addCallback('afterCreate', function($obj, $identifier, $data, $fixtures) {
if(isset($fixtures['Group']['admin'])) { if(isset($fixtures['Group']['admin'])) {
$adminGroup = Group::get()->byId($fixtures['Group']['admin']); $adminGroup = Group::get()->byId($fixtures['Group']['admin']);
$obj->Groups()->add($adminGroup); $obj->Groups()->add($adminGroup);
} }
}); });
$member = $factory->createObject('Member'); // not in admin group $member = $factory->createObject('Member'); // not in admin group
$admin = $factory->createObject('AdminMember'); // in admin group $admin = $factory->createObject('AdminMember'); // in admin group
### Full Test Example ### Full Test Example
:::php :::php
class MyObjectTest extends SapphireTest { class MyObjectTest extends SapphireTest {
protected $factory; protected $factory;
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
$factory = Injector::inst()->create('FixtureFactory'); $factory = Injector::inst()->create('FixtureFactory');
// Defines a "blueprint" for new objects // Defines a "blueprint" for new objects
$factory->define('MyObject', array( $factory->define('MyObject', array(
'MyProperty' => 'My Default Value' 'MyProperty' => 'My Default Value'
)); ));
$this->factory = $factory; $this->factory = $factory;
} }
function testSomething() { function testSomething() {
$MyObjectObj = $this->factory->createObject( $MyObjectObj = $this->factory->createObject(
'MyObject', 'MyObject',
array('MyOtherProperty' => 'My Custom Value') array('MyOtherProperty' => 'My Custom Value')
); );
// $myPageObj->MyProperty = My Default Value // $myPageObj->MyProperty = My Default Value
// $myPageObj->MyOtherProperty = My Custom Value // $myPageObj->MyOtherProperty = My Custom Value
} }
} }

View File

@ -31,7 +31,7 @@ test.
**Fake Object**: A substitute object that simply replaces a real object with the same interface, and returns a **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. 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 "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 context of each call to them, setting expectations of which, and what order, methods will be invoked and what parameters
will be passed. will be passed.

View File

@ -111,7 +111,7 @@ All command-line arguments are documented on
### Via the "sake" Wrapper on Command Line ### Via the "sake" Wrapper on Command Line
The [sake](/topics/commandline) executable that comes with SilverStripe can trigger a customized 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, While the custom test runner a handy tool, its also more limited than using `phpunit` directly,
particularly around formatting test output. particularly around formatting test output.

View File

@ -1,48 +1,46 @@
# Testing Email # 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 ## 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 For this to work, you need to send emails using the `Email` class,
send emails in your SilverStripe application. Here is a simple example of how you might do this: 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 :::php
$e = new Email(); $e = new Email();
$e->To = "someone@example.com"; $e->To = "someone@example.com";
$e->Subject = "Hi there"; $e->Subject = "Hi there";
$e->Body = "I just really wanted to email you and say hi."; $e->Body = "I just really wanted to email you and say hi.";
$e->send(); $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]` :::php
test, then it holds off actually sending the email, and instead lets you assert that an email was sent using this method. $this->assertEmailSent("someone@example.com", null, "/th.*e$/");
:::php The arguments are `$to`, `$from`, `$subject`, `$body`, and can take one of the three following types:
$this->assertEmailSent("someone@example.com", null, "/th.*e$/");
* A string: match exactly that string
The arguments are `$to`, `$from`, `$subject`, `$body`, and can be take one of the following three forms: * `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 ## 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. Given all of that, there is not a lot that you have to do in order to test emailing functionality in your application.
Whenever we include e-mailing functionality in our application,
* Write your SilverStripe application, using the Email class to send emails. we simply use `$this->assertEmailSent()` to check our mail has been passed to PHP `mail` in our tests.
* Write tests that trigger the email sending functionality.
* Include appropriate `$this->assertEmailSent()` calls in those tests.
That's it! That's it!
## What isn't tested ## 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 It's important to realise that this email testing doesn't actually test everything that there is to do with email.
focus of this email testing system is testing that your application is triggering emails correctly. It doesn't test The focus of this email testing system is testing that your application is triggering emails correctly.
your email infrastructure outside of the webserver. For example: 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 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 * 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 ## 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 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 explaining how we built the email test, this is a good design pattern for making other "tricky external systems" As well as explaining how we built the email test,
testable: 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 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. mail(). The default mailer is an object of type `Mailer`, which performs a normal send.