Index: ow/tests/helpers.py
===================================================================
--- ow/tests/helpers.py	(revision ceae1586c5f3212b82536b7b50d6d3728ab9d181)
+++ ow/tests/helpers.py	(revision ceae1586c5f3212b82536b7b50d6d3728ab9d181)
@@ -0,0 +1,3 @@
+def join(*args, **kwargs):
+    """ Faked join method, for mocking purposes """
+    return '/' + '/'.join([arg for arg in args])
Index: ow/tests/models/test_workout.py
===================================================================
--- ow/tests/models/test_workout.py	(revision b22a9d242e18b6f331fcb66c2fc1f40380495a6c)
+++ ow/tests/models/test_workout.py	(revision ceae1586c5f3212b82536b7b50d6d3728ab9d181)
@@ -12,4 +12,6 @@
 from ow.models.root import OpenWorkouts
 from ow.utilities import create_blob
+
+from ow.tests.helpers import join
 
 
@@ -553,2 +555,63 @@
         workout.tracking_filetype = None
         assert workout.has_fit
+
+    @patch('ow.models.workout.os')
+    @patch('ow.models.workout.save_map_screenshot')
+    def test_map_screenshot_no_gpx(self, sms, os, root):
+        workout = root['john']['1']
+        assert workout.map_screenshot is None
+        assert not os.path.abspath.called
+        assert not os.path.dirname.called
+        assert not os.path.join.called
+        assert not os.path.exists.called
+        assert not sms.called
+
+    @patch('ow.models.workout.os')
+    @patch('ow.models.workout.save_map_screenshot')
+    def test_map_screenshot_save(self, sms, os, root):
+        """
+        A workout with a tracking file has no map screenshot, one is
+        saved to the filesystem.
+        This test simply asserts the calls to the separate methods that
+        look for existing screenshots and save a new one
+        """
+        os.path.abspath.return_value = 'current_dir'
+        os.path.join.side_effect = join
+        # This forces the "save screenshot" code to be run
+        os.path.exists.return_value = False
+
+        workout = root['john']['1']
+        workout.tracking_file = 'faked gpx file'
+        workout.tracking_filetype = 'gpx'
+
+        uid = str(root['john'].uid)
+        assert workout.map_screenshot == 'ow:/static/maps/' + uid + '/1.png'
+        assert os.path.abspath.called
+        assert os.path.dirname.called
+        assert os.path.join.call_count == 2
+        assert os.path.exists.called
+        sms.assert_called_once_with(workout)
+
+    @patch('ow.models.workout.os')
+    @patch('ow.models.workout.save_map_screenshot')
+    def test_map_screenshot_do_not_save(self, sms, os, root):
+        """
+        A workout with a tracking file has a map screenshot, the path to that
+        is returned without doing anything else
+        """
+        os.path.abspath.return_value = 'current_dir'
+        os.path.join.side_effect = join
+        # This forces the "save screenshot" code NOT to be run
+        os.path.exists.return_value = True
+
+        workout = root['john']['1']
+        workout.tracking_file = 'faked gpx file'
+        workout.tracking_filetype = 'gpx'
+
+        uid = str(root['john'].uid)
+        assert workout.map_screenshot == 'ow:/static/maps/' + uid + '/1.png'
+        assert os.path.abspath.called
+        assert os.path.dirname.called
+        assert os.path.join.call_count == 2
+        assert os.path.exists.called
+        assert not sms.called
Index: ow/tests/test_utilities.py
===================================================================
--- ow/tests/test_utilities.py	(revision b22a9d242e18b6f331fcb66c2fc1f40380495a6c)
+++ ow/tests/test_utilities.py	(revision ceae1586c5f3212b82536b7b50d6d3728ab9d181)
@@ -1,7 +1,13 @@
 import os
+from datetime import timedelta
+from unittest.mock import patch
 from pyexpat import ExpatError
 from xml.dom.minidom import Element
 
 import pytest
+
+from ow.models.root import OpenWorkouts
+from ow.models.user import User
+from ow.models.workout import Workout
 
 from ow.utilities import (
@@ -16,8 +22,28 @@
     mps_to_kmph,
     kmph_to_mps,
+    save_map_screenshot
 )
 
+from ow.tests.helpers import join
+
 
 class TestUtilities(object):
+
+    @pytest.fixture
+    def john(self):
+        john = User(firstname='John', lastname='Doe',
+                    email='john.doe@example.net')
+        john.password = 's3cr3t'
+        return john
+
+    @pytest.fixture
+    def root(self, john):
+        root = OpenWorkouts()
+        root.add_user(john)
+        john['1'] = Workout(
+            duration=timedelta(minutes=60),
+            distance=30
+        )
+        return root
 
     def test_slugify(self):
@@ -54,4 +80,68 @@
     def test_kmph_to_mps(self):
         assert kmph_to_mps(30) == 30 * 0.277778
+
+    @patch('ow.utilities.os')
+    @patch('ow.utilities.subprocess')
+    def test_save_map_screenshot_no_gpx(self, subprocess, os, root, john):
+        saved = save_map_screenshot(john['1'])
+        assert not saved
+        assert not os.path.abspath.called
+        assert not os.path.dirname.called
+        assert not os.path.join.called
+        assert not os.path.exists.called
+        assert not os.makedirs.called
+        assert not subprocess.run.called
+        # even having a fit tracking file, nothing is done
+        john['1'].tracking_file = 'faked fit file'
+        john['1'].tracking_filetype = 'fit'
+        saved = save_map_screenshot(john['1'])
+        assert not saved
+        assert not os.path.abspath.called
+        assert not os.path.dirname.called
+        assert not os.path.join.called
+        assert not os.path.exists.called
+        assert not os.makedirs.called
+        assert not subprocess.run.called
+
+    @patch('ow.utilities.os')
+    @patch('ow.utilities.subprocess')
+    def test_save_map_screenshot_with_gpx(self, subprocess, os, root, john):
+        os.path.abspath.return_value = 'current_dir'
+        os.path.join.side_effect = join
+        # This mimics what happens when the directory for this user map
+        # screenshots does not exist, which means we don'have to create one
+        # (calling os.makedirs)
+        os.path.exists.return_value = False
+
+        john['1'].tracking_file = 'faked gpx content'
+        john['1'].tracking_filetype = 'gpx'
+        saved = save_map_screenshot(john['1'])
+        assert saved
+        os.path.abspath.assert_called_once
+        assert os.path.dirname.called
+        assert os.path.join.call_count == 3
+        assert os.path.exists.called
+        assert os.makedirs.called
+        subprocess.run.assert_called_once
+
+    @patch('ow.utilities.os')
+    @patch('ow.utilities.subprocess')
+    def test_save_map_screenshot_with_gpx_makedirs(
+            self, subprocess, os, root, john):
+        os.path.abspath.return_value = 'current_dir'
+        os.path.join.side_effect = join
+        # If os.path.exists returns True, makedirs is not called
+        os.path.exists.return_value = True
+
+        john['1'].tracking_file = 'faked gpx content'
+        john['1'].tracking_filetype = 'gpx'
+        saved = save_map_screenshot(john['1'])
+        assert saved
+        os.path.abspath.assert_called_once
+        assert os.path.dirname.called
+        assert os.path.join.call_count == 3
+        assert os.path.exists.called
+        assert not os.makedirs.called
+        subprocess.run.assert_called_once
 
 
Index: ow/tests/views/test_workout.py
===================================================================
--- ow/tests/views/test_workout.py	(revision b22a9d242e18b6f331fcb66c2fc1f40380495a6c)
+++ ow/tests/views/test_workout.py	(revision ceae1586c5f3212b82536b7b50d6d3728ab9d181)
@@ -429,2 +429,30 @@
                 assert response.content_type == 'application/xml'
                 assert expected_body in response.body
+
+    def test_workout_map_no_gpx(self, dummy_request):
+        request = dummy_request
+        user = request.root['john']
+        workout = user.workouts()[0]
+        response = workout_views.workout_map(workout, request)
+        assert response == {'start_point': {}}
+
+    def test_workout_map(self, dummy_request):
+        request = dummy_request
+        user = request.root['john']
+        workout = user.workouts()[0]
+        # to ensure has_gpx returns true
+        workout.tracking_filetype = 'gpx'
+        gpx_file_path = os.path.join(
+            os.path.dirname(os.path.dirname(__file__)),
+            'fixtures/20131013.gpx')
+        with patch.object(workout, 'tracking_file') as tf:
+            with open(gpx_file_path, 'r') as gpx_file:
+                tf.open.return_value = BytesIO(gpx_file.read().encode('utf-8'))
+                response = workout_views.workout_map(workout, request)
+                assert response == {
+                    'start_point': {
+                        'elevation': None,
+                        'latitude': 37.108735040304566,
+                        'longitude': 25.472489344630546
+                    }
+                }
