diff --git a/redhen_lead.test b/redhen_lead.test
index 7fe666a..911195f 100644
--- a/redhen_lead.test
+++ b/redhen_lead.test
@@ -17,6 +17,7 @@ class RedHenLeadWebTestCase extends DrupalWebTestCase {
     'lead_form_add' => 'admin/structure/redhen/lead-forms/add',
     'lead_form_list' => 'admin/structure/redhen/lead-forms',
     'lead_form_configure_basic' => 'admin/structure/redhen/lead-forms/manage/basic',
+    'lead_form_configure_static' => 'admin/structure/redhen/lead-forms/manage/static',
     'block_configure' => 'admin/structure/block',
     'block_configure_basic' => 'admin/structure/block/manage/redhen_lead_form/basic/configure',
   );
@@ -50,8 +51,124 @@ class RedHenLeadWebTestCase extends DrupalWebTestCase {
       'label' => ucwords($machine_name),
     ));
 
+    if (!empty($fields)) {
+      foreach ($fields as $field_label => $field) {
+        // Create the initial field.
+        field_create_field($field);
+
+        // Attach the field to the contact type.
+        $field_instance = array(
+          'field_name' => $field['field_name'],
+          'entity_type' => 'redhen_contact',
+          'bundle' => $machine_name,
+          'label' => $field_label,
+          'widget' => array(
+            'type' => 'text_textfield',
+          ),
+          'required' => isset($field['required']) ? $field['required'] : FALSE,
+        );
+        field_create_instance($field_instance);
+      }
+    }
+
     return redhen_contact_type_save($contact_type);
   }
+
+  /**
+   * Imports a RedHen Lead Form.
+   *
+   * @param $serialized_entity
+   *   A serialized string as exported by entity_export().
+   */
+  protected function importLeadform($serialized_entity) {
+    $lead_form = entity_import('redhen_lead_form', $serialized_entity);
+    entity_save('redhen_lead_form', $lead_form);
+  }
+
+  /**
+   * Attaches fields to the lead entity.
+   *
+   * @param $fields
+   *   An array of field definitions.
+   */
+  protected function createLeadField($fields) {
+    foreach ($fields as $field_label => $field) {
+      // Create the initial field.
+      field_create_field($field);
+
+      // Attach the field to the lead entity.
+      $field_instance = array(
+        'field_name' => $field['field_name'],
+        'entity_type' => 'redhen_lead',
+        'bundle' => 'redhen_lead',
+        'label' => $field_label,
+        'widget' => array(
+          'type' => 'text_textfield',
+        ),
+        'required' => isset($field['required']) ? $field['required'] : FALSE,
+      );
+      field_create_instance($field_instance);
+    }
+  }
+
+  /**
+   * Creates a new file field.
+   *
+   * @param $name
+   *   The name of the new field (all lowercase).
+   * @param $type_name
+   *   The node type that this field will be added to.
+   * @param $settings
+   *   A list of field settings that will be added to the defaults.
+   */
+  protected function createFileField($fields) {
+    foreach ($fields as $field_label => $field) {
+      field_create_field($field);
+
+      field_create_instance(array(
+        'field_name' => $field['field_name'],
+        'entity_type' => 'node',
+        'bundle' => 'page',
+        'label' => $field_label,
+        'widget' => array(
+          'type' => 'file_generic', 
+          'settings' => array(),
+        ),
+        'required' => isset($field['required']) ? $field['required'] : FALSE,
+      ));
+    }
+  }
+
+  /**
+   * Uploads a file to a node.
+   */
+  function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, $extras = array()) {
+    $langcode = LANGUAGE_NONE;
+    $edit = array(
+      "title" => $this->randomName(), 
+      'revision' => (string) (int) $new_revision,
+    );
+
+    if (is_numeric($nid_or_type)) {
+      $nid = $nid_or_type;
+    }
+    else {
+      // Add a new node.
+      $extras['type'] = $nid_or_type;
+      $node = $this->drupalCreateNode($extras);
+      $nid = $node->nid;
+      // Save at least one revision to better simulate a real site.
+      $this->drupalCreateNode(get_object_vars($node));
+      $node = node_load($nid, NULL, TRUE);
+      $this->assertNotEqual($nid, $node->vid, t('Node revision exists.'));
+    }
+
+    // Attach a file to the node.
+    $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri);
+    $this->drupalPost("node/$nid/edit", $edit, t('Save'));
+
+    return $nid;
+  }
 }
 
 
@@ -341,3 +458,212 @@ class RedHenLeadConfigurationTestCase extends RedHenLeadWebTestCase {
     );
   }
 }
+
+/**
+ * Tests end-user functionality of the RedHen Lead module.
+ */
+class RedHenLeadFunctionalityTestCase extends RedHenLeadWebTestCase {
+  private $form_data = array(
+    'basic_lead' => array(
+      'first_name' => 'Dries',
+      'last_name' => 'Buytaert',
+      'contact_field[und][0][value]' => 'Contact Field Value',
+      'lead_field[und][0][value]' => 'Lead Field Value',
+      'mail' => 'test@example.com',
+    ),
+  );
+
+  public static function getInfo() {
+    return array(
+      'name' => 'RedHen Lead Functionality',
+      'description' => 'Tests end-user functionality of the RedHen Lead module.',
+      'group' => 'RedHen',
+    );
+  }
+
+  function setUp() {
+    // Ensure required modules are enabled.
+    parent::setUp(array('entity', 'redhen', 'redhen_contact', 'redhen_lead', 'redhen_lead_form', 'file'));
+
+    // Create a contact type and attach a custom field.
+    $this->createContactType('generic', array(
+      'Contact field' => array(
+        'field_name' => 'contact_field',
+        'type' => 'text',
+        'cardinality' => 1,
+        'locked' => FALSE,
+        'required' => TRUE,
+      ),
+    ));
+
+    // Create a field and attach it to the lead entity.
+    $this->createLeadField(array(
+      'Lead field' => array(
+        'field_name' => 'lead_field',
+        'type' => 'text',
+        'cardinality' => 1,
+        'locked' => FALSE,
+        'required' => TRUE,
+      ),
+    ));
+
+    // Create a static lead form that points to the user registration page.
+    $this->importLeadForm('{
+      "name" : "static",
+      "label" : "Static",
+      "weight" : 0,
+      "settings" : {
+        "contact" : { "contact_type" : "generic", "session" : 0 },
+        "cta" : {
+          "button_text" : "Static CTA",
+          "type" : "static",
+          "static" : { "url" : "user/register" },
+          "dynamic" : { "field" : "" },
+          "target" : "_blank"
+        },
+        "block_config" : {
+          "regions" : { "seven" : -1, "bartik" : "sidebar_second" },
+          "visibility" : {
+            "path" : { "visibility" : 1, "pages" : "node" },
+            "role" : { "roles" : { "3" : 0, "1" : 0, "2" : 0, "4" : 0 } },
+            "user" : { "custom" : "0" },
+            "node_type" : { "types" : {
+                "page" : 0,
+                "story" : 0
+              }
+            }
+          },
+          "settings" : { "title" : "Static Form Title!" }
+        }
+      }
+    }');
+
+    // Create a file field and attach it to nodes of type page.
+    $this->createFileField(array(
+      'File field' => array(
+        'field_name' => 'file_field',
+        'type' => 'file',
+        'cardinality' => 1,
+        'locked' => FALSE,
+        'required' => FALSE,
+      ),
+    ));
+
+    // Create a node of type "page."
+    $files = $this->drupalGetTestFiles('text');
+    $this->admin_user = $this->drupalCreateUser(array(
+      'access administration pages',
+      'access content',
+      'administer nodes',
+      'create page content',
+      'edit any page content',
+    ));
+    $this->drupalLogin($this->admin_user);
+    $this->uploadNodeFile($files[0], 'file_field', 'page');
+    $this->drupalCreateNode();
+    $this->drupalLogout();
+
+    // Create a dynamic lead form that points to the user registration page.
+    $this->importLeadForm('{
+      "name" : "dynamic",
+      "label" : "Dynamic",
+      "weight" : 0,
+      "settings" : {
+        "contact" : { "contact_type" : "generic", "session" : 1 },
+        "cta" : {
+          "button_text" : "Dynamic CTA",
+          "type" : "dynamic",
+          "static" : { "url" : "" },
+          "dynamic" : { "field" : "file_field" },
+          "target" : "_self"
+        },
+        "block_config" : {
+          "regions" : { "seven" : -1, "bartik" : "sidebar_second" },
+          "visibility" : {
+            "path" : { "visibility" : 0, "pages" : "" },
+            "role" : { "roles" : { "3" : 0, "1" : 0, "2" : 0, "4" : 0 } },
+            "user" : { "custom" : "0" },
+            "node_type" : { "types" : {
+                "page" : "page",
+                "story" : 0
+              }
+            }
+          },
+          "settings" : { "title" : "Dynamic Form Title!" }
+        }
+      }
+    }');
+  }
+
+  function tearDown() {
+    parent::tearDown();
+  }
+
+  /**
+   * Tests basic user interactions with a lead form.
+   */
+  public function testBasicUserInteraction() {
+    // Test that the static form title makes it to the form.
+    $this->drupalGet('node');
+    $this->assertText('Static Form Title!', t('Static form title appears on lead form block'));
+
+    // Test that CTA button text makes it to the form.
+    $this->assertFieldByName('op', 'Static CTA', t('Static form CTA appears on lead form.'));
+
+    // Test that CTA target makes it to the form.
+    $xpath = $this->buildXPathQuery('//form[@target=:target]', array(':target' => '_blank'));
+    $this->assertFieldByXPath($xpath, NULL, t('Static form target appears on lead form.'));
+
+    // Contact Type fields specified in configuration appear on the form.
+    $this->assertFieldByName('contact_field[und][0][value]', NULL, t('Contact Type field appears on lead form.'));
+
+    // Lead fields specified appear on the form.
+    $this->assertFieldByName('lead_field[und][0][value]', NULL, t('Lead field appears on lead form.'));
+
+    // Check form submission errors
+    $this->drupalPost(NULL, array(), 'Static CTA');
+    $this->assertText('First name field is required.', t('First name field required.'));
+    $this->assertText('Last name field is required.', t('Last name field required.'));
+    $this->assertText('E-mail field is required.', t('E-mail field required.'));
+    $this->assertText('Lead field field is required.', t('Lead field required.'));
+    $this->assertText('Contact field field is required.', t('Contact field required.'));
+
+    // Check more "exotic" form submission errors.
+    $this->drupalPost(NULL, array(
+      'mail' => 'test@example',
+      'first_name' => $this->randomString(256),
+    ), 'Static CTA');
+    $this->assertText('First name cannot be longer than 255 characters', t('Prevented overly long first name.'));
+    $this->assertText('The e-mail address test@example is not valid.', t('Prevented invalid e-mail address.'));
+
+    // Sucessful submission with a static action redirects to the static path.
+    $this->drupalPost(NULL, $this->form_data['basic_lead'], 'Static CTA');
+    $this->assertTrue(strpos($this->url, 'user/register') !== FALSE, t('Static form successfully redirected to static path.'));
+
+    // Basic tests re-run for the dynamic form.
+    $this->drupalGet('node/1');
+    $this->assertText('Dynamic Form Title!', t('Dynamic form title appears on lead form block'));
+    $this->assertFieldByName('op', 'Dynamic CTA', t('Dynamic form CTA appears on lead form.'));
+    $xpath = $this->buildXPathQuery('//form[@target=:target]', array(':target' => '_self'));
+    $this->assertFieldByXPath($xpath, NULL, t('Dynamic form target appears on lead form.'));
+
+    // Dynamic form does not appear if the dynamic field specified is empty.
+    $this->drupalGet('node/2');
+    $this->assertNoText('Dynamic Form Title!', t('Dynamic form does not appear when dynamic field is empty.'));
+
+    // Successful submission with a dynamic action redirects dynamically.
+    $this->drupalPost('node/1', $this->form_data['basic_lead'], 'Dynamic CTA');
+    $this->assertTrue(strpos($this->url, '/sites/default/files/') !== FALSE, t('Dynamic form successfully redirected to file.'));
+
+    // Submitting a form when session caching and viewing the form again shows only the button and no fields.
+    $headers = $this->drupalGetHeaders(TRUE);
+    $cookie = $headers[0]['set-cookie'];
+    $this->drupalGet('node/1', array(), array('cookie' => $cookie));
+    $this->assertFieldByName('op', 'Dynamic CTA', t('Dynamic form CTA appears on lead form.'));
+    $this->assertNoFieldByName('contact_field[und][0][value]', NULL, t('Contact Type field does not appear on lead form with session caching.'));
+    $this->assertNoFieldByName('first_name', NULL, t('First name field does not appear on lead form with session caching.'));
+    $this->assertNoFieldByName('last_name', NULL, t('Last name field does not appear on lead form with session caching.'));
+    $this->assertNoFieldByName('mail', NULL, t('E-mail field does not appear on lead form with session caching.'));
+    $this->assertFieldByName('lead_field[und][0][value]', NULL, t('Lead field appears on lead form with session caching.'));
+  }
+}
