diff --git a/src/Tests/CasGatewayTest.php b/src/Tests/CasGatewayTest.php
new file mode 100644
index 0000000..d3bf49d
--- /dev/null
+++ b/src/Tests/CasGatewayTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasGatewayTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Test case for CAS gateway feature.
+ */
+class CasGatewayTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CAS Gateway',
+      'description' => 'Test CAS Gateway ("Check to see if user is already logged in") feature.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * Test the CAS Gateway functionality of the user is not logged in.
+   */
+  function testCasGatewayLoggedOut() {
+    $node1 = $this->drupalCreateNode();
+
+    variable_set('cas_check_frequency', CAS_CHECK_ONCE);
+    $this->drupalGet('');
+    $this->assertTrue($this->redirect_count == 2, 'Polled CAS server on first request.');
+    $this->drupalGet('');
+    $this->assertEqual($this->redirect_count, 0, 'Did not poll CAS server on second request.');
+    $this->drupalGet('node/' . $node1->nid);
+    $this->assertEqual($this->redirect_count, 0, 'Did not poll CAS server on third request.');
+    $this->assertFalse($this->loggedInUser);
+
+    variable_set('cas_check_frequency', CAS_CHECK_ALWAYS);
+    $this->drupalGet('');
+    $this->assertTrue($this->redirect_count == 2, 'Polled CAS server on first request');
+    $this->drupalGet('');
+    $this->assertTrue($this->redirect_count == 2, 'Polled CAS server on second request');
+    $this->drupalGet('node/' . $node1->nid);
+    $this->assertEqual($this->redirect_count == 2, 'Polled CAS server on third request');
+    $this->assertFalse($this->loggedInUser);
+  }
+
+  /**
+   * Test the CAS Gateway functionality of the user is logged in.
+   */
+  function testCasGatewayLoggedIn() {
+    // Create a user.
+    $cas_name = $this->randomName();
+    $account = $this->casCreateUser($cas_name);
+    $this->setCasUser($cas_name);
+
+    variable_set('cas_check_frequency', CAS_CHECK_ONCE);
+    $this->drupalGet('');
+    $this->assertLoggedIn($account);
+    // Logging out should immediately log a user back in.
+    $this->drupalGet('user/logout');
+    $this->assertLoggedIn($account);
+
+    variable_set('cas_check_frequency', CAS_CHECK_ALWAYS);
+    $this->drupalGet('');
+    $this->assertLoggedIn($account);
+    // Logging out should immediately log a user back in.
+    $this->drupalGet('user/logout');
+    $this->assertLoggedIn($account);
+  }
+}
diff --git a/src/Tests/CasLoginBlockTest.php b/src/Tests/CasLoginBlockTest.php
new file mode 100644
index 0000000..69c14c3
--- /dev/null
+++ b/src/Tests/CasLoginBlockTest.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasLoginBlockTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Tests the visibility and functionality of the CAS login block.
+ */
+class CasLoginBlockTest extends CasWebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'CAS login block',
+      'description' => 'Tests the CAS login block.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Enable the CAS login block.
+    $admin_user = $this->drupalCreateUser(array('administer blocks'));
+    $this->drupalLogin($admin_user);
+    $edit = array(
+      'blocks[user_login][region]' => '-1',
+      'blocks[cas_login][region]' => 'sidebar_first',
+    );
+    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+    $this->drupalLogout();
+  }
+
+  /**
+   * Asserts that the CAS login block is shown or not shown.
+   *
+   * @param $visible
+   *   Whether or not the CAS login block is expected to be shown.
+   *
+   * @return
+   *  TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  function assertCasLoginBlock($visible) {
+    $xpath = '//div[@id=block-cas-0]/*';
+    $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-cas-login'));
+    if ($visible) {
+      return $this->assertFieldByXPath($xpath, NULL, t('CAS login block found.'));
+    }
+    else {
+      return $this->assertNoFieldByXPath($xpath, NULL, t('CAS login block not found.'));
+    }
+  }
+
+  /**
+   * Tests the visibility and functionality of the CAS login block.
+   */
+  function testCasLoginBlock() {
+    $account = $this->casCreateUser();
+    $this->setCasUser($account);
+
+    // Verify that the block is shown on some pages, but not on others.
+    $this->drupalGet('');
+    $this->assertCasLoginBlock(TRUE);
+
+    $this->drupalGet('user');
+    $this->assertCasLoginBlock(FALSE);
+
+    $this->drupalGet('user/1');
+    $this->assertCasLoginBlock(TRUE);
+
+    // Log in using the login block, and verify redirection works.
+    $edit = array();
+    $submit = t(variable_get('cas_login_invite', CAS_LOGIN_INVITE_DEFAULT));
+
+    $this->drupalPost('', $edit, $submit);
+    $this->assertLoggedIn($account);
+    $this->assertUrl('node');
+
+    // Block should not be shown to logged in users.
+    $this->assertCasLoginBlock(FALSE);
+  }
+}
diff --git a/src/Tests/CasLoginRedirectionTest.php b/src/Tests/CasLoginRedirectionTest.php
new file mode 100644
index 0000000..c0ec28f
--- /dev/null
+++ b/src/Tests/CasLoginRedirectionTest.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasLoginRedirectionTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Test case to test user login behavior.
+ */
+class CasLoginRedirectionTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Login redirection',
+      'description' => 'Test CAS user login redirection.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  /**
+   * Verify login redirection for an existing user.
+   */
+  function testExistingUserLoginRedirection() {
+    $node1 = $this->drupalCreateNode();
+    $node2 = $this->drupalCreateNode();
+    $node3 = $this->drupalCreateNode();
+
+    // Create a CAS user.
+    $account = $this->casCreateUser();
+    $cas_name = $account->cas_name;
+    $this->setCasUser($cas_name);
+
+    // Test going to 'cas'
+    $this->drupalGet('cas');
+    $this->assertLoggedIn($account);
+    $this->assertUrl('');
+    $this->drupalLogout();
+
+    // Test going to 'cas?destination=node/$node1->nid'
+    $destination = "node/$node1->nid";
+    $this->drupalGet('cas', array('query' => array('destination' => $destination)));
+    $this->assertLoggedIn($account);
+    $this->assertUrl($destination);
+    $this->drupalLogout();
+
+    // Use the login block on $node2.
+    $destination = "node/$node2->nid";
+    variable_set('cas_login_form', CAS_ADD_LINK);
+    $edit = array('cas_identifier' => TRUE);
+    $this->drupalPost($destination, $edit, t('Log in'));
+    $this->assertLoggedIn($account);
+    $this->assertUrl($destination);
+    $this->drupalLogout();
+
+    // Use the regular login page, without a destination.
+    $edit = array('cas_identifier' => TRUE);
+    $this->drupalPost('user/login', $edit, t('Log in'));
+    $this->assertLoggedIn($account);
+    $this->assertUrl('user');
+    $this->drupalLogout();
+
+    // Use the regular login page, with a destination.
+    $destination = "node/$node3->nid";
+    $edit = array('cas_identifier' => TRUE);
+    $this->drupalPost('user/login', $edit, t('Log in'), array('query' => array('destination' => $destination)));
+    $this->assertLoggedIn($account);
+    $this->assertUrl($destination);
+    $this->drupalLogout();
+
+    // External destinations are not allowed.
+    $destination = '';
+    $this->drupalGet('cas', array('query' => array('destination' => 'http://example.com/node/3')));
+    $this->assertLoggedIn($account);
+    $this->assertUrl($destination);
+    $this->drupalLogout();
+  }
+
+  /**
+   * Verify login redirection for a new user.
+   */
+  function testNewUserLoginRedirection() {
+    // Initial login without a destination goes to front page.
+    $cas_name = $this->randomName();
+    $this->casLogin($cas_name);
+    $this->assertUrl('');
+    $this->drupalLogout();
+
+    // Initial login with redirection goes to specified destination.
+    $node = $this->drupalCreateNode();
+    variable_set('cas_first_login_destination', "node/$node->nid");
+    $cas_name = $this->randomName();
+    $account = $this->casLogin($cas_name);
+    $this->assertUrl("node/$node->nid");
+    $this->drupalLogout();
+
+    // The second login should not be redirected.
+    $this->casLogin($cas_name);
+    $this->assertUrl('');
+    $this->drupalLogout();
+
+    // Initial login with a admin-created account goes to the specified
+    // destination.
+    $account = $this->casCreateUser();
+    $this->casLogin($account);
+    $this->assertUrl("node/$node->nid");
+    $this->drupalLogout();
+
+    // The second login should not be redirected.
+    $this->casLogin($account);
+    $this->assertUrl('');
+    $this->drupalLogout();
+  }
+}
diff --git a/src/Tests/CasLogoutRedirectionTest.php b/src/Tests/CasLogoutRedirectionTest.php
new file mode 100644
index 0000000..842cf56
--- /dev/null
+++ b/src/Tests/CasLogoutRedirectionTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasLogoutRedirectionTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Test case to test user logout behavior.
+ */
+class CasLogoutRedirectionTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Logout redirection',
+      'description' => 'Test CAS user logout redirection.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  /**
+   * Test redirection on user logout.
+   */
+  function testLogoutRedirection() {
+    $account = $this->casCreateUser();
+
+    $this->casLogin($account);
+    $this->drupalGet('caslogout');
+    $this->assertText('Logged out. No redirection provided.');
+    $this->assertLoggedOut();
+
+    // Verify the destination parameter may be passed on logout, i.e.,
+    // caslogout?destination=node
+    $destination = 'node';
+    $this->casLogin($account);
+    $this->drupalGet('caslogout', array('query' => array('destination' => $destination)));
+    $this->assertText(t('Logged out. Continue to @url.', array('@url' => url($destination, array('absolute' => TRUE)))));
+    $this->assertLoggedOut();
+
+    // Verify that remote destination parameters are not allowed.
+    $destination = 'http://example.com/?query=yes#fragment';
+    $this->casLogin($account);
+    $this->drupalGet('caslogout', array('query' => array('destination' => $destination)));
+    $this->assertText(t('Logged out. No redirection provided.'));
+    $this->assertLoggedOut();
+
+    // Verify 'cas_logout_destination' works for a variety of destinations,
+    // including remote destinations.
+    $destinations = array('<front>', 'http://example.com/?query=yes#fragment', 'node/1');
+    foreach ($destinations as $destination) {
+      variable_set('cas_logout_destination', $destination);
+      $this->casLogin($account);
+      $this->drupalGet('caslogout');
+      $this->assertText(t('Logged out. Continue to @url.', array('@url' => url($destination, array('absolute' => TRUE)))));
+      $this->assertLoggedOut();
+    }
+
+    // Verify 'cas_logout_destination' can be overwritten by passing the
+    // destination query string.
+    variable_set('cas_logout_destination', 'http://example.com/');
+    $destination = 'node/1';
+    $this->casLogin($account);
+    $this->drupalGet('caslogout', array('query' => array('destination' => $destination)));
+    $this->assertText(t('Logged out. Continue to @url.', array('@url' => url($destination, array('absolute' => TRUE)))));
+    $this->assertLoggedOut();
+  }
+}
diff --git a/src/Tests/CasRequiredLoginTest.php b/src/Tests/CasRequiredLoginTest.php
new file mode 100644
index 0000000..283e77f
--- /dev/null
+++ b/src/Tests/CasRequiredLoginTest.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasRequiredLoginTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Test case for CAS force login feature.
+ */
+class CasRequiredLoginTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Required Login',
+      'description' => 'Test CAS required login redirection.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  /**
+   * Test redirection forced by cas_access and cas_pages variables.
+   */
+  function testCasPages() {
+    $node1 = $this->drupalCreateNode();
+    $node2 = $this->drupalCreateNode();
+    $account = $this->casCreateUser();
+    $this->setCasUser($account);
+
+    $this->drupalGet("node/$node2->nid");
+
+    // Enable required login for $node.
+    variable_set('cas_access', 0);
+    variable_set('cas_pages', "node/$node1->nid\nnode/$node2->nid");
+
+    // Visit the node and verify we are logged in.
+    $this->drupalGet("node/$node2->nid");
+    $this->assertLoggedIn($account);
+    $this->assertUrl("node/$node2->nid");
+    $this->drupalLogout();
+
+    // Invert the access restrictions. Verify we can get the access the node
+    // without restriction.
+    variable_set('cas_access', 1);
+    $this->drupalGet("node/$node1->nid");
+    $this->assertField('name', t('Username field found.'), t('Logout'));
+    $this->assertField('pass', t('Password field found.'), t('Logout'));
+
+    // Verify that accessing any other page redirects to the login page.
+    $this->clearCasUser();
+    $this->drupalGet('node');
+    $this->assertText('No CAS name provided.');
+  }
+
+  /**
+   * Test redirection prevented by cas_exclude.
+   */
+  function testCasExclude() {
+    $node = $this->drupalCreateNode();
+    $account = $this->casCreateUser();
+    $this->setCasUser($account);
+
+    variable_set('cas_check_frequency', CAS_CHECK_ONCE);
+    variable_set('cas_exclude', "node/$node->nid");
+
+    // Visit an excluded page and ensure we did not try to log in.
+    $this->drupalGet("node/$node->nid");
+    $this->assertField('name', t('Username field found.'), t('Logout'));
+    $this->assertField('pass', t('Password field found.'), t('Logout'));
+
+    // Visit another page and ensure we logged in.
+    $this->drupalGet('node');
+    $this->assertLoggedIn($account);
+    $this->assertUrl('node');
+  }
+}
diff --git a/src/Tests/CasSingleSignOutTest.php b/src/Tests/CasSingleSignOutTest.php
new file mode 100644
index 0000000..af61e44
--- /dev/null
+++ b/src/Tests/CasSingleSignOutTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasSingleSignOutTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Test CAS Single Sign-Out.
+ */
+class CasSingleSignOutTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Single Sign-Out',
+      'description' => 'Test CAS Single Sign-Out.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  function testSingleSignOut() {
+    // Create a user, and log in.
+    $cas_name = $this->randomName();
+    $account = $this->casCreateUser($cas_name);
+    $this->casLogin($account);
+
+    cas_test_single_sign_out($cas_name);
+    $this->assertLoggedOut();
+
+    // @todo: Add additional tests for other methods of logging in (especially
+    //   methods coming from cas_pages).
+  }
+
+  function testSingleSignOutDoubleEncode() {
+    // Create a user, and log in.
+    $cas_name = $this->randomName();
+    $account = $this->casCreateUser($cas_name);
+    $this->casLogin($account);
+
+    cas_test_single_sign_out($cas_name, TRUE);
+    $this->assertLoggedOut();
+
+    // @todo: Add additional tests for other methods of logging in (especially
+    //   methods coming from cas_pages).
+  }
+}
diff --git a/src/Tests/CasUserAdminTest.php b/src/Tests/CasUserAdminTest.php
new file mode 100644
index 0000000..460a083
--- /dev/null
+++ b/src/Tests/CasUserAdminTest.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasUserAdminTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+class CasUserAdminTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'User administration',
+      'description' => 'Test CAS user administration.',
+      'group' => 'Central Authentication Service'
+    );
+  }
+
+  /**
+   * Registers, modifies, and deletes a CAS user using User API hooks.
+   */
+  function testCASUserHooks() {
+    // Create a test account.
+    $account = $this->drupalCreateUser();
+    $uid = $account->uid;
+
+    // Add a CAS username.
+    $cas_name = $this->randomName();
+    $edit['cas_name'] = $cas_name;
+    $account = user_save($account, $edit);
+    $this->assertEqual($cas_name, $account->cas_name, t('CAS username %cas_name successfully created.', array('%cas_name' => $cas_name)));
+
+    // Reload the account and ensure the CAS name is still present.
+    $account = user_load($uid);
+    $this->assertEqual($cas_name, $account->cas_name, t('CAS username %cas_name successfully saved.', array('%cas_name' => $cas_name)));
+
+    // Load the account by the CAS username.
+    $account = cas_user_load_by_name($cas_name);
+    $this->assertEqual($uid, $account->uid, t('Loaded the correct account with CAS username %cas_name.', array('%cas_name' => $cas_name)));
+
+    // Change the CAS username.
+    $cas_new_name = $this->randomName();
+    $account = user_load($uid);
+    $edit = array('cas_name' => $cas_new_name);
+    user_save($account, $edit);
+    $account = user_load($uid);
+    $this->assertEqual($cas_new_name, $account->cas_name, t('CAS username %cas_name successfully updated.', array('%cas_name' => $cas_new_name)));
+    $this->assertEqual(count($account->cas_names), 1, t('Only one CAS username is present.'));
+    $account = cas_user_load_by_name($cas_name);
+    $this->assertFalse($account, t('Could not load account using old CAS username.'));
+
+    // Remove the CAS username.
+    $account = user_load($uid);
+    $edit = array('cas_name' => NULL);
+    user_save($account, $edit);
+    $account = user_load($uid);
+    $this->assertFalse($account->cas_name, t('CAS username successfully deleted.'));
+    $this->assertEqual(count($account->cas_names), 0, t('No CAS usernames are present.'));
+
+    // Attempt to load by a non-existant CAS username.
+    $account = cas_user_load_by_name($cas_new_name);
+    $this->assertFalse($account, t('Could not load account with non-existent CAS username.'));
+
+    // Verify that all CAS usernames have been removed from {cas_user}.
+    $cas_uid_count = db_select('cas_user')
+      ->condition('cas_name', array($cas_name, $cas_new_name), 'IN')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    $this->assertEqual($cas_uid_count, 0, t('CAS usernames successfully removed from {cas_user}.'));
+  }
+  /**
+   * Tests adding a user with a CAS username in the administrative interface.
+   */
+  function testUserAdd() {
+    $this->drupalLogin($this->admin_user);
+
+    // Register a user with a CAS username.
+    $cas_name = $this->randomName();
+    $edit = array(
+      'name' => $this->randomName(),
+      'mail' => $this->randomName() . '@example.com',
+      'cas_name' => $cas_name,
+      'pass[pass1]' => $pass = $this->randomString(),
+      'pass[pass2]' => $pass,
+      'notify' => FALSE,
+    );
+    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
+    $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created');
+
+    $this->drupalGet('admin/people');
+    $this->assertText($edit['name'], 'User found in list of users');
+    $this->assertText($edit['cas_name'], 'CAS username found in list of users');
+
+    // Verify that duplicate CAS usernames are not allowed.
+    $edit = array(
+      'name' => $this->randomName(),
+      'mail' => $this->randomName() . '@example.com',
+      'cas_name' => $cas_name,
+      'pass[pass1]' => $pass = $this->randomString(),
+      'pass[pass2]' => $pass,
+      'notify' => FALSE,
+    );
+    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
+    $this->assertText(t('The CAS username is already in use on this site.'), 'CAS username already in use.');
+  }
+
+  /**
+   * Tests adding a CAS user in the administrative interface.
+   */
+  function testCasUserAdd() {
+    $this->drupalLogin($this->admin_user);
+
+    // Add 3 CAS users.
+    $edit = array();
+    for ($i = 0; $i < 3; $i++) {
+      $cas_names[] = $this->randomName();
+    }
+    $edit['cas_name'] = implode("\n", $cas_names);
+    $this->drupalPost('admin/people/cas/create', $edit, t('Create new account(s)'));
+    $this->assertText(t('The following 3 CAS usernames were created: @cas_names', array('@cas_names' => implode(', ', $cas_names))), 'Users created');
+
+    // Verify the users show up in the list of all users.
+    $this->drupalGet('admin/people');
+    foreach ($cas_names as $cas_name) {
+      $this->assertNoUniqueText($cas_name, 'User and CAS username found in list of users');
+    }
+
+    // Attempt to add one of the users again and see that it fails.
+    $edit['cas_name'] = $cas_names[0];
+    $this->drupalPost('admin/people/cas/create', $edit, t('Create new account(s)'));
+    $this->assertText(t('The CAS username is already in use on this site.'), 'CAS username already in use.');
+  }
+}
diff --git a/src/Tests/CasUserTest.php b/src/Tests/CasUserTest.php
new file mode 100644
index 0000000..bc96546
--- /dev/null
+++ b/src/Tests/CasUserTest.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasUserTest.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\cas\Tests\CasWebTestBase;
+
+/**
+ * Test case to test user editing behavior.
+ */
+class CasUserTest extends CasWebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'User behavior',
+      'description' => 'Test CAS user behavior, including auto-registration and user editing.',
+      'group' => 'Central Authentication Service',
+    );
+  }
+
+  /**
+   * Tests automatically registering a user on login.
+   */
+  function testCasAutoRegister() {
+    $cas_name = $this->randomName();
+    $this->setCasUser($cas_name);
+
+    // Test that the user is not automatically registered.
+    variable_set('cas_user_register', FALSE);
+    $this->drupalGet('cas');
+    $this->assertRaw(t('No account found for %cas_name.', array('%cas_name' => $cas_name)));
+
+    // Ensure that the login result is not cached.
+    $cas_name = $this->randomName();
+    $this->setCasUser($cas_name);
+    $this->drupalGet('cas');
+    $this->assertRaw(t('No account found for %cas_name.', array('%cas_name' => $cas_name)));
+
+    // Test that the user is automatically registered.
+    variable_set('cas_user_register', TRUE);
+    $this->drupalGet('cas');
+    $this->loggedInUser = cas_user_load_by_name($cas_name, TRUE);
+    $this->assertRaw(t('Logged in via CAS as %cas_username.', array('%cas_username' => $cas_name)));
+    $this->drupalLogout();
+
+    // Create a new user.
+    $user2 = $this->drupalCreateUser();
+    $cas_name = $user2->name;
+    $this->setCasUser($cas_name);
+
+    // Test that CAS does not hijack an existing username.
+    $this->drupalGet('cas');
+    $this->assertRaw(t('A new account could not be created for %cas_name. The username is already in use on this site.', array('%cas_name' => $cas_name)));
+  }
+
+  function testUserEdit() {
+    $cas_name = $this->randomName();
+    $account = $this->casCreateUser($cas_name, array('change own username'));
+
+    $this->casLogin($cas_name);
+
+    // Standard user page.
+    variable_set('cas_hide_email', FALSE);
+    variable_set('cas_hide_password', FALSE);
+    $this->drupalGet("user/$account->uid/edit");
+    $this->assertField('mail', 'E-mail field is present.');
+    $this->assertField('current_pass', 'Current password field is present.');
+    $this->assertField('pass[pass1]', 'Existing password field 1 is present.');
+    $this->assertField('pass[pass2]', 'Existing password field 2 is present.');
+    $edit = array(
+      'mail' => $mail = $this->randomName() . '@example.com',
+      'current_pass' => $account->pass_raw,
+    );
+    $this->drupalPost("user/$account->uid/edit", $edit, t('Save'));
+    $this->assertFieldByName('mail', $mail);
+
+    // Hide the password, ensure edits can be made without providing
+    // the current password.
+    variable_set('cas_hide_password', TRUE);
+    $this->drupalGet("user/$account->uid/edit");
+    $this->assertNoField('current_pass', 'Current password field is not present.');
+    $this->assertNoField('pass[pass1]', 'Existing password field 1 is not present.');
+    $this->assertNoField('pass[pass2]', 'Existing password field 2 is not present.');
+    $edit = array(
+      'mail' => $mail = $this->randomName() . '@example.com',
+    );
+    $this->drupalPost("user/$account->uid/edit", $edit, t('Save'));
+    $this->assertFieldByName('mail', $mail);
+
+    // Hide the e-mail field as well, ensure that it is not visible.
+    variable_set('cas_hide_email', TRUE);
+    $this->drupalGet("user/$account->uid/edit");
+    $this->assertNoField('mail', 'E-mail field is not present.');
+  }
+
+  function testNameToken() {
+    $account = $this->casCreateUser();
+    $this->casLogin($account);
+
+    $this->assertToken('[cas:name]', $account->cas_name);
+  }
+
+  function testCaseInsensitiveLogin() {
+    $account = $this->casCreateUser();
+    $this->casLogin(strtoupper($account->cas_name));
+    $this->assertLoggedIn($account);
+  }
+
+}
diff --git a/src/Tests/CasWebTestBase.php b/src/Tests/CasWebTestBase.php
new file mode 100644
index 0000000..91273e2
--- /dev/null
+++ b/src/Tests/CasWebTestBase.php
@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\cas\Tests\CasWebTestBase.
+ */
+
+namespace Drupal\cas\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+class CasWebTestBase extends WebTestBase {
+
+  protected $admin_user;
+
+  public static $modules = array('cas', 'cas_test', 'cas_server');
+
+  /**
+   * Helper class for CAS tests.
+   *
+   * Creates an administrative user and downloads phpCAS.
+   */
+  function setUp() {
+
+    parent::setUp();
+
+    // Create admin user.
+    $this->admin_user = $this->drupalCreateUser(array('administer users', 'administer cas'));
+
+    // Download and extract in PHPCAS.
+    $this->downloadExtractPhpCas('1.3.2');
+  }
+
+  /**
+   * Download and extract phpCAS.
+   *
+   * Sets the 'cas_library_dir' variable to the directory where phpCAS
+   * is downloaded.
+   *
+   * @param string $version
+   *   The phpCAS version number to download and extract.
+   */
+  function downloadExtractPhpCas($version) {
+    // Find the most URL of the most recent phpCAS version.
+    $directory = 'CAS-' . $version;
+    $filename = 'CAS-' . $version . '.tgz';
+    $url = 'http://downloads.jasig.org/cas-clients/php/' . $version . '/' . $filename;
+
+    // Avoid downloading the file dozens of times
+    $simpletest_cache = $this->originalFileDirectory . '/simpletest/cas';
+    if (!file_exists($simpletest_cache)) {
+      mkdir($simpletest_cache);
+    }
+
+    // Local archive name.
+    $local_archive = $simpletest_cache . '/' . $filename;
+    $cas_library_dir = $simpletest_cache . '/' . $directory;
+
+    // Begin single threaded code.
+    if (function_exists('sem_get')) {
+      $semaphore = sem_get(ftok(__FILE__, 1));
+      sem_acquire($semaphore);
+    }
+
+    // Download and extact the archive, but only in one thread.
+    if (!file_exists($local_archive)) {
+      $local_archive = system_retrieve_file($url, $local_archive, FALSE, FILE_EXISTS_REPLACE);
+    }
+    if (!file_exists($cas_library_dir)) {
+      // Extract the files.
+      $archiver = archiver_get_archiver($local_archive);
+      $archiver->extract($simpletest_cache);
+    }
+    if (function_exists('sem_get')) {
+      sem_release($semaphore);
+    }
+    // End single threaded code.
+
+    // Verify that files were successfully extracted.
+    $this->assertTrue(file_exists($cas_library_dir . '/CAS.php'), t('CAS.php found in @cas_library_dir.', array('@cas_library_dir' => $cas_library_dir)));
+
+    // Set the CAS library directory.
+    $config = \Drupal::config('cas.settings');
+    $config->set('cas_library_dir', $cas_library_dir);
+    $config->save();
+  }
+
+  /**
+   * Create a CAS user with the specified username.
+   *
+   * @param $cas_name
+   *   The CAS username. If omitted, a CAS username will be automatically
+   *   generated.
+   * @param $permissions
+   *   An array of permissions to assign to the created user.
+   *
+   * @return
+   *   A user account object. The CAS username is present in the cas_name
+   *   field.
+   */
+  function casCreateUser($cas_name = NULL, $permissions = array('access comments', 'access content', 'post comments', 'skip comment approval')) {
+    // Create user.
+    $account = $this->drupalCreateUser($permissions);
+    $pass_raw = $account->pass_raw;
+
+    // Add CAS username.
+    if (empty($cas_name)) {
+      $cas_name = $this->randomName();
+    }
+    $edit['cas_name'] = $cas_name;
+    $account = user_save($account, $edit);
+
+    // Restore password.
+    $account->pass_raw = $pass_raw;
+    return $account;
+  }
+
+  /**
+   * Log in a CAS user with the internal browser.
+   *
+   * @param $account
+   *   A user object with a valid CAS username field, or the CAS username as a
+   *   string.
+   * @param $attributes
+   *   Additional attributes for the CAS user.
+   */
+  function casLogin($account, $attributes = array()) {
+    if ($this->loggedInUser) {
+      $this->drupalLogout();
+    }
+
+    // Log in the user.
+    $cas_name = $this->setCasUser($account, $attributes);
+    $this->drupalGet('cas');
+
+    $pass = $this->assertLink(t('Log out'), 0, t('CAS user %cas_name successfully logged in.', array('%cas_name' => $cas_name)), t('User login'));
+    if ($pass) {
+      $this->loggedInUser = cas_user_load_by_name($cas_name, TRUE, TRUE);
+    }
+  }
+
+  /**
+   * Set the CAS username and attributes for the next CAS login request.
+   *
+   * @param $account
+   *   A user object with a valid CAS username field, or the CAS username as a
+   *   string.
+   * @param $attributes
+   *   Additional attributes for the CAS user.
+   *
+   * @return
+   *   The CAS username.
+   */
+  function setCasUser($account, $attributes = array()) {
+    $cas_name = is_object($account) ? $account->cas_name : $account;
+    $cas_user = array('name' => $cas_name, 'attributes' => $attributes);
+    variable_set('cas_test_cas_user', $cas_user);
+    return $cas_name;
+  }
+
+  /**
+   * Clear the CAS username and attributes for the next CAS login request.
+   */
+  function clearCasUser() {
+    variable_del('cas_test_cas_user');
+  }
+
+  /**
+   * Assert that the user has logged in.
+   *
+   * @return
+   *  TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  function assertLoggedIn($account) {
+    $pass = $this->assertLink(t('Log out'), 0, t('CAS user %cas_name successfully logged in.', array('%cas_name' => $account->cas_name)), t('User login'));
+    if ($pass) {
+      $this->loggedInUser = $account;
+    }
+    return $pass;
+  }
+
+  /**
+   * Assert that the user has been logged out.
+   *
+   * @return
+   *  TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  function assertLoggedOut() {
+    $this->drupalGet('user');
+    $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
+    $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));
+    if ($pass) {
+      $this->loggedInUser = FALSE;
+    }
+    return $pass;
+  }
+
+  /**
+   * Assert the value of the token.
+   *
+   * @param $token
+   *   A token to evaluate for the current CAS user.
+   * @param $value
+   *   The expected value after the token is evaluated.
+   * @param $message
+   *   The message to display along with the assertion.
+   *
+   * @return
+   *  TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  function assertToken($token, $value, $message = '') {
+    $options = array(
+      'query' => array(
+        'token' => $token,
+        'name' => $this->loggedInUser->cas_name,
+      ),
+    );
+    $path = 'cas_test/token';
+    $out = $this->drupalGet($path, $options);
+    return $this->assertEqual($out, $value, $message, 'Token');
+  }
+
+}
