diff --git a/soitool/soi.py b/soitool/soi.py
index 5a177c63d81263db229c57a4e43f83aa79170910..5f5585817db26df29453cf05f21dfa8cae584647 100644
--- a/soitool/soi.py
+++ b/soitool/soi.py
@@ -11,7 +11,6 @@ from rectpack import (
     skyline,
     guillotine,
 )
-from soitool.modules.module_table import TableModule
 
 # functions to sort modules by different criteria
 
@@ -158,6 +157,10 @@ class SOI:
         Currently the following are implemented: 'none', 'area', 'width',
         'height'. Please refer to the STRING_TO_ALGORITHM_INITIAL_SORT
         variable.
+    modules : list of modules
+        initial modules of SOI
+    attachments : list of attachment modules
+        initial attachment modules of SOI
 
     """
 
@@ -207,6 +210,8 @@ class SOI:
         algorithm_bin="BFF",
         algorithm_pack="MaxRectsBl",
         algorithm_sort="area",
+        modules=[],
+        attachments=[],
     ):
 
         # populate date-related arguments if they are not supplied by the user
@@ -230,6 +235,8 @@ class SOI:
         self.classification = classification
         self.orientation = orientation
         self.placement_strategy = placement_strategy
+        self.modules = modules
+        self.attachments = attachments
 
         # the following propertiese are relevant when self.placement_strategy
         # is "auto". They are used by rectpack
@@ -240,34 +247,6 @@ class SOI:
         # functions to call after reorganization
         self.reorganization_listeners = []
 
-        # NOTE
-        # * test modules, just to have something show up on screen
-        # * not valid until reorganize has been run, as the positions must be
-        #   updated
-        self.modules = [
-            {
-                "widget": TableModule(),
-                "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell1"},
-            },
-            {
-                "widget": TableModule(),
-                "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell2"},
-            },
-            {
-                "widget": TableModule(),
-                "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell3"},
-            },
-        ]
-
-        # NOTE
-        # test attachments, just to have something show up on screen
-        self.attachments = [
-            {
-                "widget": TableModule(),
-                "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell1"},
-            }
-        ]
-
         self.reorganize()
 
     def add_reorganization_listener(self, function):
diff --git a/soitool/soi_workspace_widget.py b/soitool/soi_workspace_widget.py
index 76b5531b35690ce19984de8cdd938edd232b8bb6..4c00e20aaaa6232bf75b7364505741b7bbdb8714 100644
--- a/soitool/soi_workspace_widget.py
+++ b/soitool/soi_workspace_widget.py
@@ -3,12 +3,18 @@
 Meant for use inside of tabs in our program.
 
 """
-from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, \
-    QLabel
+from PySide2.QtWidgets import (
+    QWidget,
+    QHBoxLayout,
+    QVBoxLayout,
+    QPushButton,
+    QLabel,
+)
 from soitool.soi import SOI, ModuleType
 from soitool.module_list import ModuleList
 from soitool.inline_editable_soi_view import InlineEditableSOIView
 from soitool.setup_settings import Setup
+from soitool.modules.module_table import TableModule
 
 
 class SOIWorkspaceWidget(QWidget):
@@ -21,7 +27,39 @@ class SOIWorkspaceWidget(QWidget):
     def __init__(self):
         super().__init__()
 
-        self.soi = SOI()
+        # NOTE
+        # * test modules, just to have something show up on screen
+        # * not valid until reorganize has been run, as the positions must be
+        #   updated
+        initial_modules = [
+            {
+                "widget": TableModule(),
+                "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell1"},
+            },
+            {
+                "widget": TableModule(),
+                "meta": {"x": 0, "y": 0, "page": 1, "name": "Tabell2"},
+            },
+            {
+                "widget": TableModule(),
+                "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell3"},
+            },
+        ]
+
+        # NOTE
+        # test attachments, just to have something show up on screen
+        initial_attachments = [
+            {
+                "widget": TableModule(),
+                "meta": {"x": 0, "y": 0, "page": 2, "name": "Tabell1"},
+            }
+        ]
+
+        self.soi = SOI(
+            modules=initial_modules, attachments=initial_attachments
+        )
+
+        self.soi.reorganize()
 
         self.layout_wrapper = QHBoxLayout()
         self.layout_sidebar = QVBoxLayout()
diff --git a/test/test_soi.py b/test/test_soi.py
new file mode 100644
index 0000000000000000000000000000000000000000..c065e595d82ba3831839a2d823be66b31a314e15
--- /dev/null
+++ b/test/test_soi.py
@@ -0,0 +1,145 @@
+"""Test the soi module.
+
+To view the SOI during manual testing the following snippet is useful:
+
+```python
+self.soi.reorganize()
+view = InlineEditableSOIView(self.soi)
+view.show()
+app.exec_()
+```
+"""
+
+import unittest
+from PySide2.QtWidgets import QApplication, QWidget
+from PySide2 import QtGui
+from PySide2.QtGui import QPalette, QColor
+from soitool.soi import SOI
+from soitool.modules.module_base import ModuleBase
+
+if isinstance(QtGui.qApp, type(None)):
+    app = QApplication([])
+else:
+    app = QtGui.qApp
+
+
+class Meta(type(ModuleBase), type(QWidget)):
+    """Used as a metaclass to enable multiple inheritance."""
+
+
+class TestModule(ModuleBase, QWidget, metaclass=Meta):
+    """A simple module of given width, height and color for testing."""
+
+    def __init__(self, color, width, height, *args, **kwargs):
+        super(TestModule, self).__init__(*args, **kwargs)
+        self.setAutoFillBackground(True)
+        palette = self.palette()
+        palette.setColor(QPalette.Window, QColor(color))
+        self.setPalette(palette)
+        self.setGeometry(0, 0, width, height)
+
+    def get_size(self):
+        """Override."""
+        size = self.size()
+        return size.width(), size.height()
+
+    def set_pos(self, pos):
+        """Override."""
+        self.move(pos)
+
+    def render_onto_pdf(self):
+        """Not used."""
+        raise NotImplementedError()
+
+
+# The modules below have sizes that make the ideal for testing.
+# Sorting them by width should yield
+#  1. wide_module
+#  2. big_module
+#  3. tall_module
+# Sorting them by height should yield
+#  1. tall_module
+#  2. big_module
+#  3. wide_module
+# And sorting them by area should yield
+#  1. big_module
+#  2. tall_module
+#  3. wide_module
+# Note that when sorting by area the two modules 'tall_module' and
+# 'wide_module' have the same area, but 'tall_module' should come first
+# because sorting should be stable
+# https://en.wikipedia.org/wiki/Category:Stable_sorts
+# FIXME the only meta information necessary here is "name", if we assume
+#      automatic sorting..
+TEST_MODULES = [
+    {
+        "widget": TestModule("red", 100, 400),
+        "meta": {"x": 0, "y": 0, "page": 1, "name": "tall_module"},
+    },
+    {
+        "widget": TestModule("blue", 400, 100),
+        "meta": {"x": 0, "y": 0, "page": 1, "name": "wide_module"},
+    },
+    {
+        "widget": TestModule("orange", 300, 300),
+        "meta": {"x": 0, "y": 0, "page": 1, "name": "big_module"},
+    },
+]
+
+
+class TestSOI(unittest.TestCase):
+    """TestCase for the SOI class."""
+
+    def setUp(self):
+        """Prepare SOI to test on."""
+        self.soi = SOI(
+            modules=TEST_MODULES,
+            attachments=TEST_MODULES,
+            algorithm_sort="none",
+        )
+
+    def sort_modules_and_assert_order(self, sorting_algorithm, expected_order):
+        """Sort the SOI modules using algorithm and expect order.
+
+        This function was created to reduce code-duplication only.
+
+        Parameters
+        ----------
+        sorting_algorithm : str
+            name of sorting algorithm to pass to the SOI
+        expected_order : list of string names
+            names of modules in the expected order
+        """
+        self.soi.algorithm_sort = sorting_algorithm
+        self.soi.sort_modules()
+
+        self.assertEqual(
+            expected_order,
+            [module["meta"]["name"] for module in self.soi.modules],
+            f"Modules should have expected order when sorting algorithm is "
+            "set to '{sorting_algorithm}'",
+        )
+
+    def test_sort_modules_none(self):
+        """Test sort algorithm 'none'."""
+        self.sort_modules_and_assert_order(
+            "none", ["tall_module", "wide_module", "big_module"]
+        )
+
+    def test_sort_modules_width(self):
+        """Test sort algorithm 'width'."""
+        self.sort_modules_and_assert_order(
+            "width", ["wide_module", "big_module", "tall_module"]
+        )
+
+    def test_sort_modules_height(self):
+        """Test sort algorithm 'height'."""
+        self.sort_modules_and_assert_order(
+            "height", ["tall_module", "big_module", "wide_module"]
+        )
+
+    def test_sort_modules_area(self):
+        """Test sort algorithm 'area'."""
+        self.sort_modules_and_assert_order(
+            "area", ["big_module", "tall_module", "wide_module"]
+        )