diff --git a/.gitignore b/.gitignore
index 0d14ca4dd081d5d9be6e064a717f862bab407cb1..9f28e5cb447949081556e27b2b1702613de93bbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 __pycache__
 *.db
 */env
-*.coverage
\ No newline at end of file
+*.coverage
+.pytest_cache
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1c7b2a5c5eb9a5a43d2253b905e66efe5a1b61f4
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,6 @@
+stages:
+  - setup
+  - test
+
+include:
+  - local: .gitlab/server.gitlab-ci.yml
diff --git a/.gitlab/server.gitlab-ci.yml b/.gitlab/server.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..481e31bb7575c6cf3248c0eeb6174437b40ed3e2
--- /dev/null
+++ b/.gitlab/server.gitlab-ci.yml
@@ -0,0 +1,18 @@
+image: python
+
+cache:
+  paths:
+    - server/env/
+
+before_script:
+  - python --version
+  - pip install virtualenv
+  - cd server/
+  - python -m venv env
+  - source env/bin/activate
+  - pip install -r requirements.txt
+
+test-server:
+  stage: test
+  script:
+    - pytest --cov app tests/
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 415547a1d5edc3d08d5826ace0d132008d43609f..7178e876f0199277a05d047212b5e11e17644315 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -29,14 +29,12 @@
             "label": "Test Server",
             "type": "shell",
             "group": "build",
-            "command": "${workspaceFolder}/server/env/Scripts/python test.py",
+            "command": "env/Scripts/pytest.exe --cov app tests/",
             "problemMatcher": [],
             "options": {
                 "cwd": "${workspaceFolder}/server"
             },
-            "presentation": {
-                "group": "Client/Server"
-            }
+
         },
         {
             "label": "Client + Server",
diff --git a/server/requirements.txt b/server/requirements.txt
index 15f3fb46c43a7c2d2eee3959e3fce08db255efc2..31bb42fb147e77cea0e83c9af0230046efacb0b4 100644
Binary files a/server/requirements.txt and b/server/requirements.txt differ
diff --git a/server/test.py b/server/test.py
deleted file mode 100644
index f69c5a7b6da694e040f1a3980cae855cbea71bb3..0000000000000000000000000000000000000000
--- a/server/test.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from app import create_app, db
-import unittest
-import json
-
-
-class Test(unittest.TestCase):
-    def setUp(self):
-        """Before each test, set up a blank database"""
-        self.app = create_app("configmodule.TestingConfig")
-        self.app.testing = True
-
-        self.client = self.app.test_client()
-
-        with self.app.app_context():
-            db.drop_all()
-            db.create_all()
-
-    # Called after every test
-    def tearDown(self):
-        with self.app.app_context():
-            db.session.remove()
-            db.drop_all()
-
-    def test_user(self):
-        # Create user
-        rv = self.client.post(
-            "/api/users/",
-            data=json.dumps({"email": "test@test.se", "password": "abc123"}),
-        )
-        rv_dict = json.loads(rv.data.decode())
-
-        assert rv.status_code == 200
-        assert rv_dict["id"] == 1
-        assert "password" not in rv_dict
-        assert rv_dict["email"] == "test@test.se"
-
-        # Try loggin with wrong PASSWORD
-        rv = self.client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc1234"}))
-        assert rv.status_code == 401
-
-        # Try loggin with wrong Email
-        rv = self.client.post("/api/users/login", data=json.dumps({"email": "test1@test.se", "password": "abc1234"}))
-        assert rv.status_code == 401
-
-        # Try loggin with right PASSWORD
-        rv = self.client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc123"}))
-        rv_dict = json.loads(rv.data.decode())
-        assert rv.status_code == 200
-        headers = {"Authorization": "Bearer " + rv_dict["access_token"]}
-
-        # Get the current user
-        rv = self.client.get("/api/users/", headers=headers)
-        rv_dict = json.loads(rv.data.decode())
-        assert rv.status_code == 200
-        assert rv_dict["email"] == "test@test.se"
-
-        rv = self.client.put("/api/users/", data=json.dumps({"name": "carl carlsson"}), headers=headers)
-        rv_dict = json.loads(rv.data.decode())
-        assert rv.status_code == 200
-        assert rv_dict["name"] == "Carl Carlsson"
-
-    def test_empty(self):
-        # Try loggin withou any users
-        rv = self.client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc123"}))
-        assert rv.status_code == 401
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/server/tests/__init__.py b/server/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d8c1354ffd04237f06f2b2883e86c175bd511d4
--- /dev/null
+++ b/server/tests/__init__.py
@@ -0,0 +1,18 @@
+import pytest
+from app import create_app, db
+
+
+@pytest.fixture
+def app():
+    app = create_app("configmodule.TestingConfig")
+
+    with app.app_context():
+        db.drop_all()
+        db.create_all()
+
+    return app
+
+
+@pytest.fixture
+def client(app):
+    return app.test_client()
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ee2664724c4a273d5c3f26a4539ebecb1cfcb70
--- /dev/null
+++ b/server/tests/test_app.py
@@ -0,0 +1,42 @@
+from tests import app, client
+import json
+
+
+def test_app(client):
+
+    # Create user
+    rv = client.post(
+        "/api/users/",
+        data=json.dumps({"email": "test@test.se", "password": "abc123"}),
+    )
+    rv_dict = json.loads(rv.data.decode())
+
+    assert rv.status_code == 200
+    assert rv_dict["id"] == 1
+    assert "password" not in rv_dict
+    assert rv_dict["email"] == "test@test.se"
+
+    # Try loggin with wrong PASSWORD
+    rv = client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc1234"}))
+    assert rv.status_code == 401
+
+    # Try loggin with wrong Email
+    rv = client.post("/api/users/login", data=json.dumps({"email": "test1@test.se", "password": "abc1234"}))
+    assert rv.status_code == 401
+
+    # Try loggin with right PASSWORD
+    rv = client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc123"}))
+    rv_dict = json.loads(rv.data.decode())
+    assert rv.status_code == 200
+    headers = {"Authorization": "Bearer " + rv_dict["access_token"]}
+
+    # Get the current user
+    rv = client.get("/api/users/", headers=headers)
+    rv_dict = json.loads(rv.data.decode())
+    assert rv.status_code == 200
+    assert rv_dict["email"] == "test@test.se"
+
+    rv = client.put("/api/users/", data=json.dumps({"name": "carl carlsson"}), headers=headers)
+    rv_dict = json.loads(rv.data.decode())
+    assert rv.status_code == 200
+    assert rv_dict["name"] == "Carl Carlsson"