diff --git a/tests/test_shell.py b/tests/test_shell.py
new file mode 100644
index 0000000000..850b57f14e
--- /dev/null
+++ b/tests/test_shell.py
@@ -0,0 +1,321 @@
+# Copyright 2012 OpenStack LLC.
+# All Rights Reserved
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import os
+import mock
+
+from openstackclient import shell as os_shell
+from tests import utils
+
+
+DEFAULT_AUTH_URL = 'http://127.0.0.1:5000/v2.0/'
+DEFAULT_TENANT_ID = 'xxxx-yyyy-zzzz'
+DEFAULT_TENANT_NAME = 'joe_tenant'
+DEFAULT_USERNAME = 'joe_user'
+DEFAULT_PASSWORD = 'password'
+DEFAULT_REGION_NAME = 'ZZ9_Plural_Z_Alpha'
+DEFAULT_TOKEN = 'xyzpdq'
+DEFAULT_SERVICE_URL = 'http://127.0.0.1:8771/v3.0/'
+
+DEFAULT_COMPUTE_API_VERSION = '42'
+DEFAULT_IDENTITY_API_VERSION = '42.0'
+DEFAULT_IMAGE_API_VERSION = 'v42'
+
+# These values are hard-coded in the client libs
+LIB_COMPUTE_API_VERSION = '2'
+LIB_IDENTITY_API_VERSION = '2.0'
+LIB_IMAGE_API_VERSION = '1.0'
+
+
+def make_shell():
+    """Create a new command shell and mock out some bits"""
+    _shell = os_shell.OpenStackShell()
+    _shell.command_manager = mock.Mock()
+    return _shell
+
+
+class ShellTest(utils.TestCase):
+
+    def setUp(self):
+        """ Patch os.environ to avoid required auth info"""
+        global _old_env
+        fake_env = {
+            'OS_AUTH_URL': DEFAULT_AUTH_URL,
+            'OS_TENANT_ID': DEFAULT_TENANT_ID,
+            'OS_TENANT_NAME': DEFAULT_TENANT_NAME,
+            'OS_USERNAME': DEFAULT_USERNAME,
+            'OS_PASSWORD': DEFAULT_PASSWORD,
+            'OS_REGION_NAME': DEFAULT_REGION_NAME,
+        }
+        _old_env, os.environ = os.environ, fake_env.copy()
+
+        # Make a fake shell object, a helping wrapper to call it, and a quick
+        # way of asserting that certain API calls were made.
+        global shell, _shell, assert_called, assert_called_anytime
+        shell = lambda sh,cmd: sh.run(cmd.split())
+
+        # Patch out some common methods
+        #self.auth_patch = mock.patch(
+        #    'openstackclient.shell.OpenStackShell.authenticate_user')
+        #self.auth_save = self.auth_patch.start()
+        self.cmd_patch = mock.patch(
+            'openstackclient.shell.OpenStackShell.run_subcommand')
+        self.cmd_save = self.cmd_patch.start()
+
+    def tearDown(self):
+        global _old_env
+        os.environ = _old_env
+        #self.auth_patch.stop()
+        self.cmd_patch.stop()
+
+    def test_shell_args(self):
+        sh = make_shell()
+        initapp_mock = mock.Mock('default environment')
+        with mock.patch(
+            'openstackclient.shell.OpenStackShell.initialize_app',
+            initapp_mock):
+                shell(sh, 'list user')
+                initapp_mock.assert_called_with((['list', 'user']))
+
+    def test_shell_auth_password_flow(self):
+
+        def test_auth(desc, cmd_options, default_args):
+            initapp_mock = mock.Mock(desc)
+            with mock.patch(
+                'openstackclient.shell.OpenStackShell.initialize_app',
+                initapp_mock):
+                    cmd = cmd_options + ' list tenant'
+                    shell(sh, cmd)
+                    initapp_mock.assert_called_with(['list', 'tenant'])
+                    assert sh.options.os_auth_url == default_args['auth_url']
+                    assert sh.options.os_tenant_id == \
+                        default_args['tenant_id']
+                    assert sh.options.os_tenant_name == \
+                        default_args['tenant_name']
+                    assert sh.options.os_username == default_args['username']
+                    assert sh.options.os_password == default_args['password']
+                    assert sh.options.os_region_name == \
+                        default_args['region_name']
+
+        # Test the default
+        sh = make_shell()
+        test_auth('default environment', '',
+            {'auth_url': DEFAULT_AUTH_URL,
+            'tenant_id': DEFAULT_TENANT_ID,
+            'tenant_name': DEFAULT_TENANT_NAME,
+            'username': DEFAULT_USERNAME,
+            'password': DEFAULT_PASSWORD,
+            'region_name': DEFAULT_REGION_NAME,
+            })
+
+        # Test an empty environment
+        save_env, os.environ = os.environ, {}
+        sh = make_shell()
+        test_auth('empty environment', '',
+            {'auth_url': '',
+            'tenant_id': '',
+            'tenant_name': '',
+            'username': '',
+            'password': '',
+            'region_name': '',
+            })
+
+        # Test command-line arguments
+        sh = make_shell()
+        test_auth('cli arguments', '--os-auth-url ' + DEFAULT_AUTH_URL,
+            {'auth_url': DEFAULT_AUTH_URL,
+            'tenant_id': '',
+            'tenant_name': '',
+            'username': '',
+            'password': '',
+            'region_name': '',
+            })
+
+        sh = make_shell()
+        test_auth('cli arguments', '--os-tenant-id ' + DEFAULT_TENANT_ID,
+            {'auth_url': '',
+            'tenant_id': DEFAULT_TENANT_ID,
+            'tenant_name': '',
+            'username': '',
+            'password': '',
+            'region_name': '',
+            })
+
+        sh = make_shell()
+        test_auth('cli arguments', '--os-tenant-name ' + DEFAULT_TENANT_NAME,
+            {'auth_url': '',
+            'tenant_id': '',
+            'tenant_name': DEFAULT_TENANT_NAME,
+            'username': '',
+            'password': '',
+            'region_name': '',
+            })
+
+        sh = make_shell()
+        test_auth('cli arguments', '--os-username ' + DEFAULT_USERNAME,
+            {'auth_url': '',
+            'tenant_id': '',
+            'tenant_name': '',
+            'username': DEFAULT_USERNAME,
+            'password': '',
+            'region_name': '',
+            })
+
+        sh = make_shell()
+        test_auth('cli arguments', '--os-password ' + DEFAULT_PASSWORD,
+            {'auth_url': '',
+            'tenant_id': '',
+            'tenant_name': '',
+            'username': '',
+            'password': DEFAULT_PASSWORD,
+            'region_name': '',
+            })
+
+        sh = make_shell()
+        test_auth('cli arguments', '--os-region-name ' + DEFAULT_REGION_NAME,
+            {'auth_url': '',
+            'tenant_id': '',
+            'tenant_name': '',
+            'username': '',
+            'password': '',
+            'region_name': DEFAULT_REGION_NAME,
+            })
+
+        # Restore environment
+        os.environ = save_env
+
+    def test_shell_auth_token_flow(self):
+
+        def test_auth(desc, cmd_options, default_args):
+            initapp_mock = mock.Mock(desc)
+            with mock.patch(
+                'openstackclient.shell.OpenStackShell.initialize_app',
+                initapp_mock):
+                    cmd = cmd_options + ' list role'
+                    shell(sh, cmd)
+                    initapp_mock.assert_called_with(['list', 'role'])
+                    assert sh.options.os_token == default_args['os_token']
+                    assert sh.options.os_url == default_args['os_url']
+
+        token_env = {
+            'OS_TOKEN': DEFAULT_TOKEN,
+            'OS_URL': DEFAULT_SERVICE_URL,
+        }
+        save_env, os.environ = os.environ, token_env.copy()
+
+        # Test the default
+        sh = make_shell()
+        test_auth('default environment', '',
+            {'os_token': DEFAULT_TOKEN,
+            'os_url': DEFAULT_SERVICE_URL,
+            })
+
+        # Test an empty environment
+        os.environ = {}
+        sh = make_shell()
+        test_auth('empty environment', '',
+            {'os_token': '',
+            'os_url': '',
+            })
+
+        # Test command-line arguments
+        sh = make_shell()
+        test_auth('cli arguments', '--os-token ' + DEFAULT_TOKEN,
+            {'os_token': DEFAULT_TOKEN,
+            'os_url': '',
+            })
+
+        sh = make_shell()
+        test_auth('cli arguments', '--os-url ' + DEFAULT_SERVICE_URL,
+            {'os_token': '',
+            'os_url': DEFAULT_SERVICE_URL,
+            })
+
+        # Restore environment
+        os.environ = save_env
+
+    def test_shell_cli_options(self):
+
+        def test_vars(desc, cmd_options, default_args):
+            initapp_mock = mock.Mock(desc)
+            with mock.patch(
+                'openstackclient.shell.OpenStackShell.initialize_app',
+                initapp_mock):
+                    cmd = cmd_options + ' list server'
+                    shell(sh, cmd)
+                    initapp_mock.assert_called_with(['list', 'server'])
+                    print "options: %s" % sh.options
+                    print "args: %s" % default_args
+                    assert sh.options.os_compute_api_version == \
+                        default_args['compute_api_version']
+                    assert sh.options.os_identity_api_version == \
+                        default_args['identity_api_version']
+                    assert sh.options.os_image_api_version == \
+                        default_args['image_api_version']
+
+        option_env = {
+            'OS_COMPUTE_API_VERSION': DEFAULT_COMPUTE_API_VERSION,
+            'OS_IDENTITY_API_VERSION': DEFAULT_IDENTITY_API_VERSION,
+            'OS_IMAGE_API_VERSION': DEFAULT_IMAGE_API_VERSION,
+        }
+        save_env, os.environ = os.environ, option_env.copy()
+
+        # Test the default
+        sh = make_shell()
+        test_vars('default environment', '',
+            {'compute_api_version': DEFAULT_COMPUTE_API_VERSION,
+            'identity_api_version': DEFAULT_IDENTITY_API_VERSION,
+            'image_api_version': DEFAULT_IMAGE_API_VERSION,
+            })
+
+        # Test an empty environment
+        os.environ = {}
+        sh = make_shell()
+        # This should fall back to the defaults hard-coded in the client libs
+        test_vars('empty environment', '',
+            {'compute_api_version': LIB_COMPUTE_API_VERSION,
+            'identity_api_version': LIB_IDENTITY_API_VERSION,
+            'image_api_version': LIB_IMAGE_API_VERSION,
+            })
+
+        # Test command-line arguments
+        sh = make_shell()
+        test_vars('cli arguments',
+            '--os-compute-api-version ' + DEFAULT_COMPUTE_API_VERSION,
+            {'compute_api_version': DEFAULT_COMPUTE_API_VERSION,
+            'identity_api_version': LIB_IDENTITY_API_VERSION,
+            'image_api_version': LIB_IMAGE_API_VERSION,
+            })
+
+        sh = make_shell()
+        test_vars('cli arguments',
+            '--os-identity-api-version ' + DEFAULT_IDENTITY_API_VERSION,
+            {'compute_api_version': LIB_COMPUTE_API_VERSION,
+            'identity_api_version': DEFAULT_IDENTITY_API_VERSION,
+            'image_api_version': LIB_IMAGE_API_VERSION,
+            })
+
+        sh = make_shell()
+        test_vars('cli arguments',
+            '--os-image-api-version ' + DEFAULT_IMAGE_API_VERSION,
+            {'compute_api_version': LIB_COMPUTE_API_VERSION,
+            'identity_api_version': LIB_IDENTITY_API_VERSION,
+            'image_api_version': DEFAULT_IMAGE_API_VERSION,
+            })
+
+        # Restore environment
+        os.environ = save_env
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 0000000000..3535360d25
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,21 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import time
+
+import mox
+import unittest2
+
+
+class TestCase(unittest2.TestCase):
+
+    def setUp(self):
+        super(TestCase, self).setUp()
+        self.mox = mox.Mox()
+        self._original_time = time.time
+        time.time = lambda: 1234
+
+    def tearDown(self):
+        time.time = self._original_time
+        super(TestCase, self).tearDown()
+        self.mox.UnsetStubs()
+        self.mox.VerifyAll()
diff --git a/tools/test-requires b/tools/test-requires
index ce094d9f17..b0ebc3e9b1 100644
--- a/tools/test-requires
+++ b/tools/test-requires
@@ -9,3 +9,4 @@ nosexcover
 openstack.nose_plugin
 pep8==0.6.1
 sphinx>=1.1.2
+unittest2