diff --git a/core/modules/file/tests/file_test/file_test.module b/core/modules/file/tests/file_test/file_test.module
index b5c98eb..5f71a60 100644
--- a/core/modules/file/tests/file_test/file_test.module
+++ b/core/modules/file/tests/file_test/file_test.module
@@ -14,20 +14,6 @@
 const FILE_URL_TEST_CDN_2 = 'http://cdn2.example.com';
 
 /**
- * Implements hook_menu().
- */
-function file_test_menu() {
-  $items['file-test/upload'] = array(
-    'title' => 'Upload test',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('_file_test_form'),
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-  );
-  return $items;
-}
-
-/**
  * Implements hook_stream_wrappers().
  */
 function file_test_stream_wrappers() {
@@ -51,96 +37,6 @@ function file_test_stream_wrappers() {
 }
 
 /**
- * Form to test file uploads.
- */
-function _file_test_form($form, &$form_state) {
-  $form['file_test_upload'] = array(
-    '#type' => 'file',
-    '#title' => t('Upload a file'),
-  );
-  $form['file_test_replace'] = array(
-    '#type' => 'select',
-    '#title' => t('Replace existing image'),
-    '#options' => array(
-      FILE_EXISTS_RENAME => t('Appends number until name is unique'),
-      FILE_EXISTS_REPLACE => t('Replace the existing file'),
-      FILE_EXISTS_ERROR => t('Fail with an error'),
-    ),
-    '#default_value' => FILE_EXISTS_RENAME,
-  );
-  $form['file_subdir'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Subdirectory for test file'),
-    '#default_value' => '',
-  );
-
-  $form['extensions'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Allowed extensions.'),
-    '#default_value' => '',
-  );
-
-  $form['allow_all_extensions'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Allow all extensions?'),
-    '#default_value' => FALSE,
-  );
-
-  $form['is_image_file'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Is this an image file?'),
-    '#default_value' => TRUE,
-  );
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Submit'),
-  );
-  return $form;
-}
-
-/**
- * Process the upload.
- */
-function _file_test_form_submit(&$form, &$form_state) {
-  // Process the upload and perform validation. Note: we're using the
-  // form value for the $replace parameter.
-  if (!empty($form_state['values']['file_subdir'])) {
-    $destination = 'temporary://' . $form_state['values']['file_subdir'];
-    file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
-  }
-  else {
-    $destination = FALSE;
-  }
-
-  // Setup validators.
-  $validators = array();
-  if ($form_state['values']['is_image_file']) {
-    $validators['file_validate_is_image'] = array();
-  }
-
-  if ($form_state['values']['allow_all_extensions']) {
-    $validators['file_validate_extensions'] = array();
-  }
-  elseif (!empty($form_state['values']['extensions'])) {
-    $validators['file_validate_extensions'] = array($form_state['values']['extensions']);
-  }
-
-  $file = file_save_upload('file_test_upload', $validators, $destination, 0, $form_state['values']['file_test_replace']);
-  if ($file) {
-    $form_state['values']['file_test_upload'] = $file;
-    drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->uri)));
-    drupal_set_message(t('File name is @filename.', array('@filename' => $file->filename)));
-    drupal_set_message(t('File MIME type is @mimetype.', array('@mimetype' => $file->filemime)));
-    drupal_set_message(t('You WIN!'));
-  }
-  elseif ($file === FALSE) {
-    drupal_set_message(t('Epic upload FAIL!'), 'error');
-  }
-}
-
-
-/**
  * Reset/initialize the history of calls to the file_* hooks.
  *
  * @see file_test_get_calls()
diff --git a/core/modules/file/tests/file_test/file_test.routing.yml b/core/modules/file/tests/file_test/file_test.routing.yml
new file mode 100644
index 0000000..73fa7ed
--- /dev/null
+++ b/core/modules/file/tests/file_test/file_test.routing.yml
@@ -0,0 +1,6 @@
+file_test:
+  pattern: '/file-test/upload'
+  defaults:
+    _form: 'Drupal\file_test\Form\FileTestForm'
+  requirements:
+    _permission: 'access content'
diff --git a/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php b/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php
new file mode 100644
index 0000000..890a410
--- /dev/null
+++ b/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\file_test\Form\FileTestForm.
+ */
+
+namespace Drupal\file_test\Form;
+
+use Drupal\Core\Form\FormInterface;
+
+/**
+ * File test form class.
+ */
+class FileTestForm implements FormInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return '_file_test_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $form['file_test_upload'] = array(
+      '#type' => 'file',
+      '#title' => t('Upload a file'),
+    );
+    $form['file_test_replace'] = array(
+      '#type' => 'select',
+      '#title' => t('Replace existing image'),
+      '#options' => array(
+        FILE_EXISTS_RENAME => t('Appends number until name is unique'),
+        FILE_EXISTS_REPLACE => t('Replace the existing file'),
+        FILE_EXISTS_ERROR => t('Fail with an error'),
+      ),
+      '#default_value' => FILE_EXISTS_RENAME,
+    );
+    $form['file_subdir'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Subdirectory for test file'),
+      '#default_value' => '',
+    );
+
+    $form['extensions'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Allowed extensions.'),
+      '#default_value' => '',
+    );
+
+    $form['allow_all_extensions'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Allow all extensions?'),
+      '#default_value' => FALSE,
+    );
+
+    $form['is_image_file'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Is this an image file?'),
+      '#default_value' => TRUE,
+    );
+
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Submit'),
+    );
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    // Process the upload and perform validation. Note: we're using the
+    // form value for the $replace parameter.
+    if (!empty($form_state['values']['file_subdir'])) {
+      $destination = 'temporary://' . $form_state['values']['file_subdir'];
+      file_prepare_directory($destination, FILE_CREATE_DIRECTORY);
+    }
+    else {
+      $destination = FALSE;
+    }
+
+    // Setup validators.
+    $validators = array();
+    if ($form_state['values']['is_image_file']) {
+      $validators['file_validate_is_image'] = array();
+    }
+
+    if ($form_state['values']['allow_all_extensions']) {
+      $validators['file_validate_extensions'] = array();
+    }
+    elseif (!empty($form_state['values']['extensions'])) {
+      $validators['file_validate_extensions'] = array($form_state['values']['extensions']);
+    }
+
+    $file = file_save_upload('file_test_upload', $validators, $destination, 0, $form_state['values']['file_test_replace']);
+    if ($file) {
+      $form_state['values']['file_test_upload'] = $file;
+      drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->uri)));
+      drupal_set_message(t('File name is @filename.', array('@filename' => $file->filename)));
+      drupal_set_message(t('File MIME type is @mimetype.', array('@mimetype' => $file->filemime)));
+      drupal_set_message(t('You WIN!'));
+    }
+    elseif ($file === FALSE) {
+      drupal_set_message(t('Epic upload FAIL!'), 'error');
+    }
+  }
+}
