diff --git a/Gruntfile.js b/Gruntfile.js
index 5a9317c..92aa12d 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -439,6 +439,10 @@ module.exports = function(grunt) {
 			'external-http': {
 				cmd: 'phpunit',
 				args: ['-c', 'phpunit.xml.dist', '--group', 'external-http']
+			},
+			'restapi-jsclient': {
+				cmd: 'phpunit',
+				args: ['-c', 'phpunit.xml.dist', '--group', 'restapi-jsclient']
 			}
 		},
 		uglify: {
@@ -672,6 +676,11 @@ module.exports = function(grunt) {
 		'jshint:media'
 	] );
 
+	grunt.registerTask( 'restapi-jsclient', [
+		'phpunit:restapi-jsclient',
+		'qunit'
+	] );
+
 	grunt.renameTask( 'watch', '_watch' );
 
 	grunt.registerTask( 'watch', function() {
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php
index e69de29..cda1869 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-setup.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php
@@ -0,0 +1,247 @@
+<?php
+/**
+ * Unit tests covering schema initialization.
+ *
+ * Also generates the fixture data used by the wp-api.js QUnit tests.
+ *
+ * @package WordPress
+ * @subpackage REST API
+ */
+
+/**
+ * @group restapi
+ * @group restapi-jsclient
+ */
+class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
+
+	public function setUp() {
+		parent::setUp();
+
+		/** @var WP_REST_Server $wp_rest_server */
+		global $wp_rest_server;
+		$this->server = $wp_rest_server = new Spy_REST_Server;
+		do_action( 'rest_api_init' );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		remove_filter( 'rest_url', array( $this, 'test_rest_url_for_leading_slash' ), 10, 2 );
+		/** @var WP_REST_Server $wp_rest_server */
+		global $wp_rest_server;
+		$wp_rest_server = null;
+	}
+
+	public function test_expected_routes_in_schema() {
+		$routes = $this->server->get_routes();
+
+		$this->assertTrue( is_array( $routes ), '`get_routes` should return an array.' );
+		$this->assertTrue( ! empty( $routes ), 'Routes should not be empty.' );
+
+		$expected_routes = array(
+			'/',
+			'/oembed/1.0',
+			'/oembed/1.0/embed',
+			'/wp/v2',
+			'/wp/v2/posts',
+			'/wp/v2/posts/(?P<id>[\\d]+)',
+			'/wp/v2/posts/(?P<parent>[\\d]+)/revisions',
+			'/wp/v2/posts/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)',
+			'/wp/v2/pages',
+			'/wp/v2/pages/(?P<id>[\\d]+)',
+			'/wp/v2/pages/(?P<parent>[\\d]+)/revisions',
+			'/wp/v2/pages/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)',
+			'/wp/v2/media',
+			'/wp/v2/media/(?P<id>[\\d]+)',
+			'/wp/v2/types',
+			'/wp/v2/types/(?P<type>[\\w-]+)',
+			'/wp/v2/statuses',
+			'/wp/v2/statuses/(?P<status>[\\w-]+)',
+			'/wp/v2/taxonomies',
+			'/wp/v2/taxonomies/(?P<taxonomy>[\\w-]+)',
+			'/wp/v2/categories',
+			'/wp/v2/categories/(?P<id>[\\d]+)',
+			'/wp/v2/tags',
+			'/wp/v2/tags/(?P<id>[\\d]+)',
+			'/wp/v2/users',
+			'/wp/v2/users/(?P<id>[\\d]+)',
+			'/wp/v2/users/me',
+			'/wp/v2/comments',
+			'/wp/v2/comments/(?P<id>[\\d]+)',
+			'/wp/v2/settings',
+		);
+
+		$this->assertEquals( $expected_routes, array_keys( $routes ) );
+	}
+
+	public function test_build_wp_api_client_fixtures() {
+		// Set up for testing the individual endpoints.
+		// Set a current admin user.
+		$administrator = $this->factory->user->create( array(
+			'role' => 'administrator',
+		) );
+		wp_set_current_user( $administrator );
+
+		// Set up data for endpoints.
+		$post_id  = $this->factory->post->create();
+		$page_id  = $this->factory->post->create( array( 'post_type' => 'page' ) );
+		$tag_id   = $this->factory->tag->create( array( 'name' => 'test' ) );
+		$media_id = $this->factory->attachment->create_object( '/tmp/canola.jpg', 0, array(
+			'post_mime_type' => 'image/jpeg',
+			'post_excerpt'   => 'A sample caption',
+		) );
+		wp_update_post( array( 'post_content' => 'Updated content.', 'ID' => $post_id ) );
+		wp_update_post( array( 'post_content' => 'Updated content.', 'ID' => $page_id ) );
+		$comment_id = $this->factory->comment->create( array(
+			'comment_approved' => 1,
+			'comment_post_ID'  => $post_id,
+			'user_id'          => 0,
+		) );
+
+		// Generate route data for subsequent QUnit tests.
+		$routes_to_generate_data = array(
+			array(
+				'route' => '/',
+				'name'  => 'Schema',
+			),
+			array(
+				'route' => '/oembed/1.0',
+				'name'  => 'oembed',
+			),
+			array(
+				'route' => '/oembed/1.0/embed',
+				'name'  => 'oembeds',
+			),
+			array(
+				'route' => '/wp/v2/posts',
+				'name'  => 'PostsCollection',
+			),
+			array(
+				'route' => '/wp/v2/posts/' . $post_id,
+				'name'  => 'PostModel',
+			),
+			array(
+				'route' => '/wp/v2/posts/' . $post_id . '/revisions',
+				'name'  => 'postRevisions',
+			),
+			array(
+				'route' => '/wp/v2/posts/' . $post_id . '/revisions/1',
+				'name'  => 'revision',
+			),
+			array(
+				'route' => '/wp/v2/pages',
+				'name'  => 'PagesCollection',
+			),
+			array(
+				'route' => '/wp/v2/pages/' . $page_id,
+				'name'  => 'PageModel',
+			),
+			array(
+				'route' => '/wp/v2/pages/'. $page_id . '/revisions',
+				'name'  => 'pageRevisions',
+			),
+			array(
+				'route' => '/wp/v2/pages/'. $page_id . '/revisions/1',
+				'name'  => 'pageRevision',
+			),
+			array(
+				'route' => '/wp/v2/media',
+				'name'  => 'MediaCollection',
+			),
+			array(
+				'route' => '/wp/v2/media/' . $media_id,
+				'name'  => 'MediaModel',
+			),
+			array(
+				'route' => '/wp/v2/types',
+				'name'  => 'TypesCollection',
+			),
+			array(
+				'route' => '/wp/v2/types/',
+				'name'  => 'TypeModel',
+			),
+			array(
+				'route' => '/wp/v2/statuses',
+				'name'  => 'StatusesCollection',
+			),
+			array(
+				'route' => '/wp/v2/statuses/publish',
+				'name'  => 'StatusModel',
+			),
+			array(
+				'route' => '/wp/v2/taxonomies',
+				'name'  => 'TaxonomiesCollection',
+			),
+			array(
+				'route' => '/wp/v2/taxonomies/category',
+				'name'  => 'TaxonomyModel',
+			),
+			array(
+				'route' => '/wp/v2/categories',
+				'name'  => 'CategoriesCollection',
+			),
+			array(
+				'route' => '/wp/v2/categories/1',
+				'name'  => 'CategoryModel',
+			),
+			array(
+				'route' => '/wp/v2/tags',
+				'name'  => 'TagsCollection',
+			),
+			array(
+				'route' => '/wp/v2/tags/' . $tag_id,
+				'name'  => 'TagModel',
+			),
+			array(
+				'route' => '/wp/v2/users',
+				'name'  => 'UsersCollection',
+			),
+			array(
+				'route' => '/wp/v2/users/1',
+				'name'  => 'UserModel',
+			),
+			array(
+				'route' => '/wp/v2/users/me',
+				'name'  => 'me',
+			),
+			array(
+				'route' => '/wp/v2/comments',
+				'name'  => 'CommentsCollection',
+			),
+			array(
+				'route' => '/wp/v2/comments/1',
+				'name'  => 'CommentModel',
+			),
+			array(
+				'route' => '/wp/v2/settings',
+				'name'  => 'settings',
+			),
+		);
+
+		$mocked_responses = "/**\n";
+		$mocked_responses .= " * DO NOT EDIT\n";
+		$mocked_responses .= " * Auto-generated by test_build_wp_api_client_fixtures\n";
+		$mocked_responses .= " */\n";
+		$mocked_responses .= "var mockedApiResponse = {};\n\n";
+
+		foreach ( $routes_to_generate_data as $route ) {
+			$request = new WP_REST_Request( 'GET', $route['route'] );
+			$response = $this->server->dispatch( $request );
+			$data = $response->get_data();
+
+			$this->assertTrue( ! empty( $data ), $route['name'] . ' route should return data.' );
+
+			$mocked_responses .= 'mockedApiResponse.' . $route['name'] . ' = ' . json_encode( $data ) . ";\n\n";
+		}
+
+		// Save the route object for QUnit tests.
+		$file = './tests/qunit/fixtures/wp-api-generated.js';
+		file_put_contents( $file, $mocked_responses );
+
+		// Clean up our test data.
+		wp_delete_post( $post_id, true );
+		wp_delete_post( $page_id, true );
+		wp_delete_term( $tag_id, 'tags' );
+		wp_delete_attachment( $media_id );
+		wp_delete_comment( $comment_id );
+	}
+}
diff --git a/tests/qunit/fixtures/wp-api.js b/tests/qunit/fixtures/wp-api.js
index e69de29..51946b0 100644
--- a/tests/qunit/fixtures/wp-api.js
+++ b/tests/qunit/fixtures/wp-api.js
@@ -0,0 +1,54 @@
+/**
+ * @var mockedApiResponse defined in wp-api-generated.js
+ */
+
+var pathToData = {
+	'wp-json/wp/v2/': mockedApiResponse.Schema,
+	'wp-json/wp/v2/categories': mockedApiResponse.CategoriesCollection,
+	'wp-json/wp/v2/comments': mockedApiResponse.CommentsCollection,
+	'wp-json/wp/v2/media': mockedApiResponse.MediaCollection,
+	'wp-json/wp/v2/pages': mockedApiResponse.PagesCollection,
+	'wp-json/wp/v2/posts': mockedApiResponse.PostsCollection,
+	'wp-json/wp/v2/statuses': mockedApiResponse.StatusesCollection,
+	'wp-json/wp/v2/tags': mockedApiResponse.TagsCollection,
+	'wp-json/wp/v2/taxonomies': mockedApiResponse.TaxonomiesCollection,
+	'wp-json/wp/v2/types': mockedApiResponse.TypesCollection,
+	'wp-json/wp/v2/users': mockedApiResponse.UsersCollection,
+	'wp-json/wp/v2/category': mockedApiResponse.CategoryModel,
+	'wp-json/wp/v2/media1': mockedApiResponse.MediaModel,
+	'wp-json/wp/v2/page': mockedApiResponse.PageModel,
+	'wp-json/wp/v2/post': mockedApiResponse.PostModel,
+	'wp-json/wp/v2/tag': mockedApiResponse.TagModel,
+	'wp-json/wp/v2/user': mockedApiResponse.UserModel,
+	'wp-json/wp/v2/taxonomy': mockedApiResponse.TaxonomyModel,
+	'wp-json/wp/v2/status': mockedApiResponse.StatusModel,
+	'wp-json/wp/v2/type': mockedApiResponse.TypeModel
+};
+
+/**
+ * Mock the ajax callbacks for our tests.
+ *
+ * @param  {object} param The parameters sent to the ajax request.
+ *
+ * @return {Object}       A jQuery defered object that resolves with the mapped data.
+ */
+Backbone.ajax = function ( param ) {
+
+	var data,
+		request = param.url.replace( 'http://localhost/', '' );
+
+	if ( pathToData[ request ] ) {
+		data = pathToData[ request ];
+	}
+
+	// Call success handler.
+	param.success( data );
+	var deferred = jQuery.Deferred();
+
+	// Resolve the deferred with the mocked data
+	deferred.resolve( data );
+
+	// Return the deferred promise that will resolve with the expected data.
+	return deferred.promise();
+
+};
diff --git a/tests/qunit/index.html b/tests/qunit/index.html
index 58591e4..0b6930f 100644
--- a/tests/qunit/index.html
+++ b/tests/qunit/index.html
@@ -17,6 +17,11 @@
 				}
 			};
 		</script>
+		<script>
+			var wpApiSettings = {
+				'root': 'http://localhost/wp-json/'
+			};
+		</script>
 		<script src="../../src/wp-includes/js/wp-util.js"></script>
 		<script src="../../src/wp-includes/js/wp-a11y.js"></script>
 
@@ -34,6 +39,8 @@
 			<script src="fixtures/customize-settings.js"></script>
 			<script src="fixtures/customize-menus.js"></script>
 			<script src="fixtures/customize-widgets.js"></script>
+			<script src="fixtures/wp-api-generated.js"></script>
+			<script src="fixtures/wp-api.js"></script>
 		</div>
 		<p><a href="editor">TinyMCE tests</a></p>
 
@@ -43,6 +50,7 @@
 		<script src="../../src/wp-includes/js/customize-models.js"></script>
 		<script src="../../src/wp-includes/js/shortcode.js"></script>
 		<script src="../../src/wp-admin/js/customize-controls.js"></script>
+		<script src="../../src/wp-includes/js/wp-api.js"></script>
 
 		<script type='text/javascript' src='../../src/wp-includes/js/jquery/ui/core.js'></script>
 		<script type='text/javascript' src='../../src/wp-includes/js/jquery/ui/widget.js'></script>
@@ -61,6 +69,7 @@
 		<script src="wp-admin/js/customize-base.js"></script>
 		<script src="wp-admin/js/customize-header.js"></script>
 		<script src="wp-includes/js/shortcode.js"></script>
+		<script src="wp-includes/js/wp-api.js"></script>
 		<script src="wp-admin/js/customize-controls.js"></script>
 		<script src="wp-admin/js/customize-controls-utils.js"></script>
 		<script src="wp-admin/js/customize-nav-menus.js"></script>
diff --git a/tests/qunit/wp-includes/js/wp-api.js b/tests/qunit/wp-includes/js/wp-api.js
index e69de29..e8634f0 100644
--- a/tests/qunit/wp-includes/js/wp-api.js
+++ b/tests/qunit/wp-includes/js/wp-api.js
@@ -0,0 +1,195 @@
+/* global wp */
+( function( QUnit ) {
+	module( 'wpapi' );
+
+	QUnit.test( 'API Loaded correctly', function( assert ) {
+		var done = assert.async();
+		assert.expect( 2 );
+
+		assert.ok( wp.api.loadPromise );
+
+		wp.api.loadPromise.done( function() {
+			assert.ok( wp.api.models );
+			done();
+		} );
+
+	} );
+
+	// The list of collections we should check.
+	var collectionClassNames = [
+		'Categories',
+		'Comments',
+		'Media',
+		'Pages',
+		'Posts',
+		'Statuses',
+		'Tags',
+		'Taxonomies',
+		'Types',
+		'Users'
+	];
+
+	// Collections that should get helpers tested.
+	var collectionHelperTests = [
+		{
+			'collectionType':   'Posts',
+			'returnsModelType': 'post',
+			'supportsMethods':  {
+				'getDate':          'getDate',
+				'getRevisions':     'getRevisions',
+				'getTags':          'getTags',
+				'getCategories':    'getCategories',
+				'getAuthorUser':    'getAuthorUser',
+				'getFeaturedMedia': 'getFeaturedMedia'
+				/*'getMeta':        'getMeta', currently not supported */
+			}
+		},
+		{
+			'collectionType':   'Pages',
+			'returnsModelType': 'page',
+			'supportsMethods':  {
+				'getDate':          'getDate',
+				'getRevisions':     'getRevisions',
+				'getAuthorUser':    'getAuthorUser',
+				'getFeaturedMedia': 'getFeaturedMedia'
+			}
+		}
+	];
+
+	_.each( collectionClassNames, function( className ) {
+		QUnit.test( 'Testing ' + className + ' collection.', function( assert ) {
+			var done = assert.async();
+
+			wp.api.loadPromise.done( function() {
+				var theCollection = new wp.api.collections[ className ]();
+				assert.ok(
+					theCollection,
+					'We can instantiate wp.api.collections.' + className
+				);
+				theCollection.fetch().done( function( response ) {
+					assert.equal(
+						1,
+						theCollection.state.currentPage,
+						'We should be on page 1 of the collection in ' + className
+					);
+
+						// Should this collection have helper methods?
+						var collectionHelperTest = _.findWhere( collectionHelperTests, { 'collectionType': className } );
+
+						// If we found a match, run the tests against it.
+						if ( ! _.isUndefined( collectionHelperTest ) ) {
+
+							// Test the first returned model.
+							var firstModel = theCollection.at( 0 );
+
+							// Is the model the right type?
+							assert.equal(
+								collectionHelperTest.returnsModelType,
+								firstModel.get( 'type' ),
+								'The wp.api.collections.' + className + ' is of type ' + collectionHelperTest.returnsModelType
+							);
+
+							// Does the model have all of the expected supported methods?
+							_.each( collectionHelperTest.supportsMethods, function( method ) {
+								assert.equal(
+									'function',
+									typeof firstModel[ method ],
+									className + '.' + method + ' is a function.'
+								);
+							} );
+						}
+
+					// Trigger Qunit async completion.
+					done();
+				} );
+
+			} );
+
+		} );
+	} );
+
+	// The list of models we should check.
+	var modelsWithIdsClassNames = [
+		'Category',
+		'Media',
+		'Page',
+		'Post',
+		'Tag',
+		'User'
+	];
+
+	_.each( modelsWithIdsClassNames, function( className ) {
+
+		QUnit.test( 'Checking ' + className + ' model.' , function( assert ) {
+			var done = assert.async();
+
+			assert.expect( 2 );
+
+			wp.api.loadPromise.done( function() {
+				var theModel = new wp.api.models[ className ]();
+				assert.ok( theModel, 'We can instantiate wp.api.models.' + className );
+				theModel.fetch().done( function(  ) {
+					var theModel2 = new wp.api.models[ className ]();
+					theModel2.set( 'id', theModel.attributes[0].id );
+					theModel2.fetch().done( function( response ) {
+
+						// We were able to retrieve the model.
+						assert.equal(
+							theModel.attributes[0].id,
+							theModel2.get( 'id' ) ,
+							'We should be able to get a ' + className
+						);
+
+						// Trigger Qunit async completion.
+						done();
+					} );
+				} );
+
+			} );
+
+		} );
+	} );
+
+	var modelsWithIndexes = [
+		'Taxonomy',
+		'Status',
+		'Type'
+	];
+
+	_.each( modelsWithIndexes, function( className ) {
+
+		QUnit.test( 'Testing ' + className + ' model.' , function( assert ) {
+			var done = assert.async();
+
+			assert.expect( 2 );
+
+			wp.api.loadPromise.done( function(  ) {
+
+				var theModel = new wp.api.models[ className ]();
+				assert.ok( theModel, 'We can instantiate wp.api.models.' + className );
+				theModel.fetch().done( function(  ) {
+					var theModel2 = new wp.api.models[ className ]();
+
+					if ( ! _.isUndefined( theModel.attributes[0] ) ) {
+						theModel2.set( 'id', theModel.attributes[0].id );
+					}
+
+					theModel2.fetch().done( function( response ) {
+						// We were able to retrieve the model.
+						assert.notEqual(
+							0,
+							_.keys( theModel2.attributes ).length ,
+							'We should be able to get a ' + className
+						);
+
+						// Trigger Qunit async completion.
+						done();
+					} );
+				} );
+
+			} );
+
+		} );
+	} );
+
+} )( window.QUnit );
