diff --git a/README.md b/README.md
index 47ab4a65d29317d22e41ccbd91d0f3a8dfd7f41e..b478b1e47504962bd77f2cc115da6e57c2dabea1 100644
--- a/README.md
+++ b/README.md
@@ -29,10 +29,25 @@ npm start
 Then go to [http://localhost:8601/](http://localhost:8601/) - the playground outputs the default GUI component
 
 ## Testing
+NOTE: If you're a windows user, please run these scripts in Windows `cmd.exe`  instead of Git Bash/MINGW64.
+
+Run linter, unit tests, and build.
 ```bash
 npm test
 ```
 
+Run unit tests in isolation.
+```bash
+npm run unit-test
+```
+
+Run unit tests in watch mode (watches for code changes and continuously runs tests). See [jest cli docs](https://facebook.github.io/jest/docs/en/cli.html#content) for more options.
+```bash
+npm run unit-test -- --watch
+```
+
+You may want to review the documentation for [Jest](https://facebook.github.io/jest/docs/en/api.html) and [Enzyme](http://airbnb.io/enzyme/docs/api/) as you write your tests.
+
 ## Publishing to GitHub Pages
 
 You can publish the GUI to github.io so that others on the Internet can view it.
diff --git a/package.json b/package.json
index bb5cd437aff3d7f6c83bacadbab4c434ed68ba14..4d41d6b2bdf4d0c61a84eb10f9ab9155f969e963 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
     "i18n:src": "babel src > tmp.js && rimraf tmp.js && ./scripts/build-i18n-source.js ./translations/messages/ ./translations/",
     "lint": "eslint . --ext .js,.jsx",
     "start": "npm run i18n:msgs && webpack-dev-server",
-    "test": "npm run lint && npm run build",
+    "unit-test": "jest",
+    "test": "npm run lint && npm run unit-test && npm run build",
     "watch": "webpack --progress --colors --watch"
   },
   "author": "Massachusetts Institute of Technology",
@@ -39,12 +40,14 @@
     "classnames": "2.2.5",
     "copy-webpack-plugin": "4.0.1",
     "css-loader": "0.28.3",
+    "enzyme": "^2.8.2",
     "eslint": "^3.16.1",
     "eslint-config-scratch": "^3.0.0",
     "eslint-plugin-react": "^7.0.1",
     "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
     "html-webpack-plugin": "2.28.0",
     "immutable": "3.8.1",
+    "jest": "^20.0.4",
     "lodash.bindall": "4.4.0",
     "lodash.debounce": "4.0.8",
     "lodash.defaultsdeep": "4.6.0",
@@ -65,6 +68,8 @@
     "react-modal": "2.2.2",
     "react-redux": "5.0.5",
     "react-style-proptype": "3.0.0",
+    "react-test-renderer": "^15.5.4",
+    "redux-mock-store": "^1.2.3",
     "react-tabs": "1.1.0",
     "redux": "3.7.0",
     "redux-throttle": "0.1.1",
@@ -81,5 +86,14 @@
     "webpack": "^2.4.1",
     "webpack-dev-server": "^2.4.1",
     "xhr": "2.4.0"
+  },
+  "jest": {
+    "testPathIgnorePatterns": [
+      "src/test.js"
+    ],
+    "moduleNameMapper": {
+      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",
+      "\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
+    }
   }
 }
diff --git a/test/__mocks__/fileMock.js b/test/__mocks__/fileMock.js
new file mode 100644
index 0000000000000000000000000000000000000000..59890f6a201a549387cb636d94e8159cfed59b33
--- /dev/null
+++ b/test/__mocks__/fileMock.js
@@ -0,0 +1,3 @@
+// __mocks__/fileMock.js
+
+module.exports = 'test-file-stub';
diff --git a/test/__mocks__/styleMock.js b/test/__mocks__/styleMock.js
new file mode 100644
index 0000000000000000000000000000000000000000..d988e23b7397f73ea6eacfbfd8bef34aa0782bb3
--- /dev/null
+++ b/test/__mocks__/styleMock.js
@@ -0,0 +1,3 @@
+// __mocks__/styleMock.js
+
+module.exports = {};
diff --git a/test/unit/components/__snapshots__/button.test.jsx.snap b/test/unit/components/__snapshots__/button.test.jsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..5b928b4c4fedbcafec4e60f941f6497f565cb5ac
--- /dev/null
+++ b/test/unit/components/__snapshots__/button.test.jsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ButtonComponent matches snapshot 1`] = `
+<span
+  className=""
+  onClick={[Function]}
+  role="button"
+/>
+`;
diff --git a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..39146df349974556185db47203ad60931acb5c74
--- /dev/null
+++ b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SpriteSelectorItemComponent matches snapshot when selected 1`] = `
+<div
+  className="ponies undefined"
+  onClick={[Function]}
+  style={
+    Object {
+      "alignContent": undefined,
+      "alignItems": undefined,
+      "alignSelf": undefined,
+      "flexBasis": undefined,
+      "flexDirection": undefined,
+      "flexGrow": undefined,
+      "flexShrink": undefined,
+      "flexWrap": undefined,
+      "height": undefined,
+      "justifyContent": undefined,
+      "width": undefined,
+    }
+  }
+>
+  <div
+    className=""
+    onClick={[Function]}
+  >
+    <img
+      className={undefined}
+      src="test-file-stub"
+    />
+  </div>
+  <canvas
+    className={undefined}
+    height={32}
+    width={32}
+  />
+  <div
+    className={undefined}
+  >
+    Pony sprite
+  </div>
+</div>
+`;
diff --git a/test/unit/components/button.test.jsx b/test/unit/components/button.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..80b20f50f7777fd38fcd2a4b51b47f27f08eb8dd
--- /dev/null
+++ b/test/unit/components/button.test.jsx
@@ -0,0 +1,24 @@
+/* eslint-env jest */
+const React = require('react'); // eslint-disable-line no-unused-vars
+const {shallow} = require('enzyme');
+const ButtonComponent = require('../../../src/components/button/button'); // eslint-disable-line no-unused-vars
+const renderer = require('react-test-renderer');
+
+describe('ButtonComponent', () => {
+    test('matches snapshot', () => {
+        const onClick = jest.fn();
+        const component = renderer.create(
+            <ButtonComponent onClick={onClick}/>
+        );
+        expect(component.toJSON()).toMatchSnapshot();
+    });
+
+    test('triggers callback when clicked', () => {
+        const onClick = jest.fn();
+        const componentShallowWrapper = shallow(
+            <ButtonComponent onClick={onClick}/>
+        );
+        componentShallowWrapper.simulate('click');
+        expect(onClick).toHaveBeenCalled();
+    });
+});
diff --git a/test/unit/components/sprite-selector-item.test.jsx b/test/unit/components/sprite-selector-item.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2e60f09b1949bf6edce96bc28fcc92b907107df3
--- /dev/null
+++ b/test/unit/components/sprite-selector-item.test.jsx
@@ -0,0 +1,72 @@
+/* eslint-env jest */
+const React = require('react'); // eslint-disable-line no-unused-vars
+const {shallow} = require('enzyme');
+const SpriteSelectorItemComponent = require( // eslint-disable-line no-unused-vars
+    '../../../src/components/sprite-selector-item/sprite-selector-item');
+const CostumeCanvas = require( // eslint-disable-line no-unused-vars
+    '../../../src/components/costume-canvas/costume-canvas');
+const CloseButton = require('../../../src/components/close-button/close-button'); // eslint-disable-line no-unused-vars
+const renderer = require('react-test-renderer');
+
+describe('SpriteSelectorItemComponent', () => {
+    let className;
+    let costumeURL;
+    let name;
+    let onClick;
+    let onDeleteButtonClick;
+    let selected;
+
+    // Wrap this in a function so it gets test specific states and can be reused.
+    const getComponent = function () {
+        return <SpriteSelectorItemComponent
+            className={className}
+            costumeURL={costumeURL}
+            name={name}
+            onClick={onClick}
+            onDeleteButtonClick={onDeleteButtonClick}
+            selected={selected}/>;
+    };
+
+    beforeEach(() => {
+        className = 'ponies';
+        costumeURL = 'https://scratch.mit.edu/foo/bar/pony';
+        name = 'Pony sprite';
+        onClick = jest.fn();
+        onDeleteButtonClick = jest.fn();
+        selected = true;
+    });
+
+    test('matches snapshot when selected', () => {
+        const component = renderer.create(getComponent());
+        expect(component.toJSON()).toMatchSnapshot();
+    });
+
+    test('does not have a close box when not selected', () => {
+        selected = false;
+        const componentShallowWrapper = shallow(getComponent());
+        expect(componentShallowWrapper.find(CloseButton).exists()).toBe(false);
+    });
+
+    test('triggers callback when Box component is clicked', () => {
+        const componentShallowWrapper = shallow(getComponent());
+        componentShallowWrapper.simulate('click');
+        expect(onClick).toHaveBeenCalled();
+    });
+
+    test('triggers callback when CloseButton component is clicked', () => {
+        const componentShallowWrapper = shallow(getComponent());
+        componentShallowWrapper.find(CloseButton).simulate('click');
+        expect(onDeleteButtonClick).toHaveBeenCalled();
+    });
+
+    test('creates a CostumeCanvas when a costume url is defined', () => {
+        const componentShallowWrapper = shallow(getComponent());
+        expect(componentShallowWrapper.find(CostumeCanvas).exists()).toBe(true);
+    });
+
+    test('does not create a CostumeCanvas when a costume url is null', () => {
+        costumeURL = null;
+        const componentShallowWrapper = shallow(getComponent());
+        expect(componentShallowWrapper.find(CostumeCanvas).exists()).toBe(false);
+    });
+});
diff --git a/test/unit/containers/__snapshots__/green-flag.test.jsx.snap b/test/unit/containers/__snapshots__/green-flag.test.jsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..21b5a43f6cf495b6037be21f65e559b47aed4ecd
--- /dev/null
+++ b/test/unit/containers/__snapshots__/green-flag.test.jsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`GreenFlag Container renders active state 1`] = `
+<img
+  className="undefined"
+  onClick={[Function]}
+  src="test-file-stub"
+  title="Go"
+/>
+`;
+
+exports[`GreenFlag Container renders inactive state 1`] = `
+<img
+  className=""
+  onClick={[Function]}
+  src="test-file-stub"
+  title="Go"
+/>
+`;
diff --git a/test/unit/containers/green-flag.test.jsx b/test/unit/containers/green-flag.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3c6ebfe9bf9f7f2b787d91fd13745f40a4651067
--- /dev/null
+++ b/test/unit/containers/green-flag.test.jsx
@@ -0,0 +1,40 @@
+/* eslint-env jest */
+const React = require('react'); // eslint-disable-line no-unused-vars
+const {shallow} = require('enzyme');
+const GreenFlag = require('../../../src/containers/green-flag'); // eslint-disable-line no-unused-vars
+const renderer = require('react-test-renderer');
+const VM = require('scratch-vm');
+
+describe('GreenFlag Container', () => {
+    let vm;
+    beforeEach(() => {
+        vm = new VM();
+    });
+
+    test('renders active state', () => {
+        const component = renderer.create(
+            <GreenFlag active={true} vm={vm}/>
+        );
+        expect(component.toJSON()).toMatchSnapshot();
+    });
+
+    test('renders inactive state', () => {
+        const component = renderer.create(
+            <GreenFlag active={false} vm={vm}/>
+        );
+        expect(component.toJSON()).toMatchSnapshot();
+    });
+
+    test('triggers onClick when active', () => {
+        const onClick = jest.fn();
+        const componentShallowWrapper = shallow(
+            <GreenFlag active={true} onClick={onClick} vm={vm}/>
+        );
+        componentShallowWrapper.simulate('click');
+        expect(onClick).toHaveBeenCalled();
+    });
+
+    // @todo: Test for handles key events.
+    // @todo: Test project run start.
+    // @todo: Test project run stop.
+});
diff --git a/test/unit/containers/sprite-selector-item.test.jsx b/test/unit/containers/sprite-selector-item.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9e62199b1410c9ef46fb7cd4fb930cd14ba967fa
--- /dev/null
+++ b/test/unit/containers/sprite-selector-item.test.jsx
@@ -0,0 +1,60 @@
+/* eslint-env jest */
+const React = require('react'); // eslint-disable-line no-unused-vars
+const {mount} = require('enzyme');
+import configureStore from 'redux-mock-store';
+import {Provider} from 'react-redux'; // eslint-disable-line no-unused-vars
+
+const SpriteSelectorItem = require( // eslint-disable-line no-unused-vars
+    '../../../src/containers/sprite-selector-item');
+const CloseButton = require('../../../src/components/close-button/close-button'); // eslint-disable-line no-unused-vars
+
+describe('SpriteSelectorItem Container', () => {
+    const mockStore = configureStore();
+    let className;
+    let costumeURL;
+    let name;
+    let onClick;
+    let onDeleteButtonClick;
+    let selected;
+    let id;
+    let store;
+    // Wrap this in a function so it gets test specific states and can be reused.
+    const getContainer = function () {
+        return <Provider store={store}><SpriteSelectorItem
+            className={className}
+            costumeURL={costumeURL}
+            id={id}
+            name={name}
+            onClick={onClick}
+            onDeleteButtonClick={onDeleteButtonClick}
+            selected={selected}/></Provider>;
+    };
+
+    beforeEach(() => {
+        store = mockStore();
+        className = 'ponies';
+        costumeURL = 'https://scratch.mit.edu/foo/bar/pony';
+        id = 1337;
+        name = 'Pony sprite';
+        onClick = jest.fn();
+        onDeleteButtonClick = jest.fn();
+        selected = true;
+        // Mock window.confirm() which is called when the close button is clicked.
+        global.confirm = jest.fn(() => true);
+    });
+
+    test('should confirm if the user really wants to delete the sprite', () => {
+        const wrapper = mount(getContainer());
+        wrapper.find(CloseButton).simulate('click');
+        expect(global.confirm).toHaveBeenCalled();
+        expect(onDeleteButtonClick).toHaveBeenCalledWith(1337);
+    });
+
+    test('should not delete the sprite if the user cancels', () => {
+        global.confirm = jest.fn(() => false);
+        const wrapper = mount(getContainer());
+        wrapper.find(CloseButton).simulate('click');
+        expect(global.confirm).toHaveBeenCalled();
+        expect(onDeleteButtonClick).not.toHaveBeenCalled();
+    });
+});