--- a
+++ b/tests/test_spatial.py
@@ -0,0 +1,187 @@
+import os
+
+import pytest
+from numpy.testing import (assert_almost_equal,
+                           assert_array_equal,
+                           assert_array_almost_equal)
+import numpy as np
+
+import oddt
+from oddt.spatial import (angle,
+                          dihedral,
+                          rmsd,
+                          distance,
+                          rotate)
+from .utils import shuffle_mol
+
+
+test_data_dir = os.path.dirname(os.path.abspath(__file__))
+
+ASPIRIN_SDF = """
+     RDKit          3D
+
+ 13 13  0  0  0  0  0  0  0  0999 V2000
+    3.3558   -0.4356   -1.0951 C   0  0  0  0  0  0  0  0  0  0  0  0
+    2.0868   -0.6330   -0.3319 C   0  0  0  0  0  0  0  0  0  0  0  0
+    2.0284   -0.9314    0.8534 O   0  0  0  0  0  0  0  0  0  0  0  0
+    1.0157   -0.4307   -1.1906 O   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.2079   -0.5332   -0.5260 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.9020   -1.7350   -0.6775 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -2.1373   -1.8996   -0.0586 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -2.6805   -0.8641    0.6975 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -1.9933    0.3419    0.8273 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.7523    0.5244    0.2125 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.0600    1.8264    0.3368 C   0  0  0  0  0  0  0  0  0  0  0  0
+    0.9397    2.1527   -0.2811 O   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.6931    2.6171    1.2333 O   0  0  0  0  0  0  0  0  0  0  0  0
+  1  2  1  0
+  2  3  2  0
+  2  4  1  0
+  4  5  1  0
+  5  6  2  0
+  6  7  1  0
+  7  8  2  0
+  8  9  1  0
+  9 10  2  0
+ 10 11  1  0
+ 11 12  2  0
+ 11 13  1  0
+ 10  5  1  0
+M  END
+
+"""
+
+
+def test_angles():
+    """Test spatial computations - angles"""
+
+    # Angles
+    assert_array_almost_equal(angle(np.array((1, 0, 0)),
+                                    np.array((0, 0, 0)),
+                                    np.array((0, 1, 0))), 90)
+
+    assert_array_almost_equal(angle(np.array((1, 0, 0)),
+                                    np.array((0, 0, 0)),
+                                    np.array((1, 1, 0))), 45)
+
+    # Check benzene ring angle
+    mol = oddt.toolkit.readstring('smi', 'c1ccccc1')
+    mol.make3D()
+    assert_array_almost_equal(angle(mol.coords[0],
+                                    mol.coords[1],
+                                    mol.coords[2]), 120, decimal=1)
+
+
+def test_dihedral():
+    """Test dihedrals"""
+    # Dihedrals
+    assert_array_almost_equal(dihedral(np.array((1, 0, 0)),
+                                       np.array((0, 0, 0)),
+                                       np.array((0, 1, 0)),
+                                       np.array((1, 1, 0))), 0)
+
+    assert_array_almost_equal(dihedral(np.array((1, 0, 0)),
+                                       np.array((0, 0, 0)),
+                                       np.array((0, 1, 0)),
+                                       np.array((1, 1, 1))), -45)
+
+    # Check benzene ring dihedral
+    mol = oddt.toolkit.readstring('smi', 'c1ccccc1')
+    mol.make3D()
+    assert abs(dihedral(*mol.coords[:4])) < 2.
+
+
+def test_distance():
+    mol1 = oddt.toolkit.readstring('sdf', ASPIRIN_SDF)
+    d = distance(mol1.coords, mol1.coords)
+    n_atoms = len(mol1.coords)
+    assert d.shape, (n_atoms == n_atoms)
+    assert_array_equal(d[np.eye(len(mol1.coords), dtype=bool)], np.zeros(n_atoms))
+
+    d = distance(mol1.coords, mol1.coords.mean(axis=0).reshape(1, 3))
+    assert d.shape, (n_atoms == 1)
+    ref_dist = [[3.556736951371501], [2.2058040428631056], [2.3896002745745415],
+                [1.6231668718498249], [0.7772981740050453], [2.0694947503940004],
+                [2.8600587871157184], [2.9014207091233857], [2.1850791695403564],
+                [0.9413368403116871], [1.8581710293650173], [2.365629642108773],
+                [2.975007440512798]]
+    assert_array_almost_equal(d, ref_dist)
+
+
+def test_spatial():
+    """Test spatial misc computations"""
+    mol = oddt.toolkit.readstring('smi', 'c1ccccc1')
+    mol.make3D()
+    mol2 = mol.clone
+    # Test rotation
+    assert_almost_equal(mol2.coords, rotate(mol2.coords, np.pi, np.pi, np.pi))
+
+    # Rotate perpendicular to ring
+    mol2.coords = rotate(mol2.coords, 0, 0, np.pi)
+
+    # RMSD
+    assert_almost_equal(rmsd(mol, mol2, method=None), 2.77, decimal=1)
+    # Hungarian must be close to zero (RDKit is 0.3)
+    assert_almost_equal(rmsd(mol, mol2, method='hungarian'), 0, decimal=0)
+    # Minimized by symetry must close to zero
+    assert_almost_equal(rmsd(mol, mol2, method='min_symmetry'), 0, decimal=0)
+
+
+def test_rmsd():
+    # pick one molecule from docked poses
+    mols = list(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf')))
+    mols = list(filter(lambda x: x.title == '312335', mols))
+
+    res = {
+        'method=None':
+            [4.7536, 2.5015, 2.7942, 1.1282, 0.7444, 1.6257, 4.7625,
+             2.7168, 2.5504, 1.9304, 2.6201, 3.1742, 3.2254, 4.7785,
+             4.8035, 7.8963, 2.2385, 4.8625, 3.2037],
+        'method=hungarian':
+            [0.9013, 1.0730, 1.0531, 1.0286, 0.7353, 1.4094, 0.5391,
+             1.3297, 1.0881, 1.7796, 2.6064, 3.1577, 3.2135, 0.8126,
+             1.2909, 2.5217, 2.0836, 1.8325, 3.1874],
+        'method=min_symmetry':
+            [0.9013, 1.0732, 1.0797, 1.0492, 0.7444, 1.6257, 0.5391,
+             1.5884, 1.0935, 1.9304, 2.6201, 3.1742, 3.2254, 1.1513,
+             1.5206, 2.5361, 2.2385, 1.971, 3.2037],
+        }
+
+    kwargs_grid = [{'method': None},
+                   {'method': 'hungarian'},
+                   {'method': 'min_symmetry'}]
+    for kwargs in kwargs_grid:
+        res_key = '_'.join('%s=%s' % (k, v)
+                           for k, v in sorted(kwargs.items()))
+        assert_array_almost_equal([rmsd(mols[0], mol, **kwargs)
+                                  for mol in mols[1:]],
+                                  res[res_key], decimal=4)
+
+    # test shuffled rmsd
+    for _ in range(5):
+        for kwargs in kwargs_grid:
+            # dont use method=None in shuffled tests
+            if kwargs['method'] is None:
+                continue
+            res_key = '_'.join('%s=%s' % (k, v)
+                               for k, v in sorted(kwargs.items()))
+            assert_array_almost_equal([rmsd(mols[0],
+                                            shuffle_mol(mol),
+                                            **kwargs)
+                                       for mol in mols[1:]],
+                                      res[res_key], decimal=4)
+
+
+def test_rmsd_errors():
+    mol = oddt.toolkit.readstring('smi', 'c1ccccc1')
+    mol.make3D()
+    mol.addh()
+    mol2 = next(oddt.toolkit.readfile('sdf', os.path.join(test_data_dir, 'data/dude/xiap/actives_docked.sdf')))
+
+    for method in [None, 'hungarian', 'min_symmetry']:
+        with pytest.raises(ValueError, match='Unequal number of atoms'):
+            rmsd(mol, mol2, method=method)
+
+        for _ in range(5):
+            with pytest.raises(ValueError, match='Unequal number of atoms'):
+                rmsd(shuffle_mol(mol), shuffle_mol(mol2), method=method)