Repository: resulyrt93/pytest-sqlalchemy-mock (language: python)

Issue:
0.1.1 Release
We will have auto mocker component


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index b6e4761..a8dc0bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,7 +80,7 @@ target/
 # IPython
 profile_default/
 ipython_config.py
-
+.idea
 # pyenv
 .python-version
 
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..eafbbe9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,25 @@
+# To install the git pre-commit hook run:
+#   pre-commit install
+# To update the pre-commit hooks run:
+#   pre-commit install-hooks
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.2.0
+    hooks:
+      - id: trailing-whitespace
+      - id: end-of-file-fixer
+        exclude: '.*\.pth$'
+      - id: debug-statements
+  - repo: https://github.com/PyCQA/isort
+    rev: 5.10.1
+    hooks:
+      - id: isort
+  - repo: https://github.com/PyCQA/flake8
+    rev: 4.0.1
+    hooks:
+      - id: flake8
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v2.32.1
+    hooks:
+      - id: pyupgrade
+        args: [ --py36-plus ]
diff --git a/README.md b/README.md
index 63b6514..348e408 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,71 @@
 # pytest-sqlalchemy-mock
-pytest plugin to mock sqlalchemy models
+[![PyPI version](https://badge.fury.io/py/pytest-sqlalchemy-mock.svg)](https://badge.fury.io/py/pytest-sqlalchemy-mock)
+
+This plugin provides pytest fixtures to create an in-memory DB instance on tests and dump your raw test data.
+
+## Installation
+```
+pip install pytest-sqlalchemy-mock
+```
+
+## Usage
+Let's assume you have a SQLAlchemy declarative base and some models with it.
+
+**models.py**
+```python
+from sqlalchemy import Column, Integer, String
+from sqlalchemy.orm import declarative_base
+
+Base = declarative_base()
+
+
+class User(Base):
+    __tablename__ = "user"
+
+    id = Column(Integer, primary_key=True)
+    name = Column(String)
+```
+Firstly SQLAlchemy base class which is used for declare models should be passed with `sqlalchemy_declarative_base` fixture in `conftest.py`
+
+**conftest.py**
+```python
+@pytest.fixture(scope="function")
+def sqlalchemy_declarative_base():
+    return Base
+```
+Then in test functions you can get mocked_session fixture to make query in mocked DB.
+
+**test_user_table.py**
+```python
+def test_mocked_session_user_table(mocked_session):
+    user_data = mocked_session.execute("SELECT * from user;").all()
+    assert user_data == []
+```
+Also you can dump your mock data to DB before start testing via `sqlalchemy_mock_config` fixture like following.
+
+**conftest.py**
+```python
+@pytest.fixture(scope="function")
+def sqlalchemy_mock_config():
+    return [("user", [
+        {
+            "id": 1,
+            "name": "Kevin"
+        },
+        {
+            "id": 2,
+            "name": "Dwight"
+        }
+    ])]
+```
+**test_user_table.py**
+```python
+def test_mocked_session_user_class(mocked_session):
+    user = mocked_session.query(User).filter_by(id=2).first()
+    assert user.name == "Dwight"
+```
+
+## Upcoming Features
+* Mock with decoratorsss
+* Support to load data from `.json` and `.csv`
+* Async SQLAlchemy support
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..780ae47
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,5 @@
+sqlalchemy
+flake8
+pytest
+pre-commit
+sqlalchemy
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..14d8ec2
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,15 @@
+[tool:pytest]
+# This section sets configuration for all invocations of py.test,
+# both standalone cmdline and running via setup.py
+norecursedirs =
+    .git
+    *.egg
+    build
+    dist
+
+[bdist_wheel]
+universal = 1
+
+[flake8]
+exclude =
+    tests/conftest.py
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..50820ea
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+import os
+
+from setuptools import setup
+
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+
+setup(
+    name='pytest-sqlalchemy-mock',
+    license='MIT',
+    description='pytest sqlalchemy plugin for mock',
+    # long_description=read("README.rst"),
+    version='0.1.1',
+    author='Resul Yurttakalan',
+    author_email='resulyrt93@gmail.com',
+    url='https://github.com/resulyrt93/pytest-sqlalchemy-mock',
+    py_modules=['pytest_sqlalchemy_mock'],
+    entry_points={'pytest11': ['sqlalchemy = pytest_sqlalchemy_mock']},
+    install_requires=['pytest>=2.0', 'sqlalchemy'],
+    classifiers=[
+        'Framework :: Pytest',
+        'Development Status :: 3 - Alpha',
+        'Topic :: Software Development :: Testing',
+        'Intended Audience :: Developers',
+        'Operating System :: OS Independent',
+    ]
+)


Test patch:
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
new file mode 100644
index 0000000..6f06f25
--- /dev/null
+++ b/.github/workflows/tests.yaml
@@ -0,0 +1,28 @@
+name: Python package
+
+on: [ pull_request ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [ "3.7", "3.8", "3.9", "3.10" ]
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r requirements-dev.txt
+      - name: Run pre commit hooks
+        run: |
+          pre-commit run --all-files --show-diff-on-failure
+      - name: Test with pytest
+        run: |
+          pytest tests
diff --git a/pytest_sqlalchemy_mock.py b/pytest_sqlalchemy_mock.py
new file mode 100644
index 0000000..67ec730
--- /dev/null
+++ b/pytest_sqlalchemy_mock.py
@@ -0,0 +1,84 @@
+from typing import TYPE_CHECKING, List, Tuple
+
+import pytest
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+if TYPE_CHECKING:
+    from sqlalchemy.orm import Session
+
+
+class ModelMocker:
+    # TODO make type annotations
+    _base = None
+    _mock_config: List[Tuple[str, List]] = None
+
+    def __init__(self, session, base, mock_config):
+        self._session: Session = session
+        self._base = base
+        self._mock_config = mock_config
+
+    def get_model_class_with_table_name(self, table_name: str):
+        for mapper in self._base.registry.mappers:
+            cls = mapper.class_
+            if cls.__tablename__ == table_name:
+                return cls
+
+    def create_all(self):
+        for model_config in self._mock_config:
+            table_name, data = model_config
+            model_class = self.get_model_class_with_table_name(table_name)
+            if model_class:
+                for datum in data:
+                    instance = model_class(**datum)
+                    self._session.add(instance)
+        self._session.commit()
+
+
+@pytest.fixture(scope="session")
+def connection_url():
+    return "sqlite:///:memory:"
+
+
+@pytest.fixture(scope="function")
+def engine(connection_url):
+    return create_engine(connection_url)
+
+
+@pytest.fixture(scope="function")
+def sqlalchemy_declarative_base():
+    return
+
+
+@pytest.fixture(scope="function")
+def sqlalchemy_mock_config():
+    return
+
+
+@pytest.fixture(scope="function")
+def connection(engine, sqlalchemy_declarative_base):
+    if sqlalchemy_declarative_base:
+        sqlalchemy_declarative_base.metadata.create_all(engine)
+    return engine.connect()
+
+
+@pytest.fixture(scope="function")
+def session(connection):
+    session: Session = sessionmaker()(bind=connection)
+    yield session
+    session.close()
+
+
+@pytest.fixture(scope="function")
+def mocked_session(
+    connection, sqlalchemy_declarative_base, sqlalchemy_mock_config
+):
+    session: Session = sessionmaker()(bind=connection)
+
+    if sqlalchemy_declarative_base and sqlalchemy_mock_config:
+        ModelMocker(
+            session, sqlalchemy_declarative_base, sqlalchemy_mock_config
+        ).create_all()
+
+    yield session
+    session.close()
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..6816aef
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,15 @@
+import pytest
+
+from pytest_sqlalchemy_mock import *
+from tests.data import MockData
+from tests.db import Base
+
+
+@pytest.fixture(scope="function")
+def sqlalchemy_declarative_base():
+    return Base
+
+
+@pytest.fixture(scope="function")
+def sqlalchemy_mock_config():
+    return [("user", MockData.USER_DATA)]
diff --git a/tests/data.py b/tests/data.py
new file mode 100644
index 0000000..c492fcd
--- /dev/null
+++ b/tests/data.py
@@ -0,0 +1,22 @@
+from datetime import datetime
+
+
+class MockData:
+    USER_DATA = [
+        {
+            "id": 1,
+            "name": "Kevin",
+            "surname": "Malone",
+            "is_admin": False,
+            "city": "NY",
+            "join_date": datetime(2011, 7, 27, 16, 2, 8),
+        },
+        {
+            "id": 2,
+            "name": "Dwight",
+            "surname": "Schrute",
+            "is_admin": True,
+            "city": "PA",
+            "join_date": datetime(2009, 11, 20, 21, 3, 12),
+        }
+    ]
diff --git a/tests/db.py b/tests/db.py
new file mode 100644
index 0000000..666590c
--- /dev/null
+++ b/tests/db.py
@@ -0,0 +1,15 @@
+from sqlalchemy import Boolean, Column, DateTime, Integer, String, func
+from sqlalchemy.orm import declarative_base
+
+Base = declarative_base()
+
+
+class User(Base):
+    __tablename__ = "user"
+
+    id = Column(Integer, primary_key=True)
+    name = Column(String)
+    surname = Column(String)
+    is_admin = Column(Boolean, default=False)
+    city = Column(String)
+    join_date = Column(DateTime(timezone=True), server_default=func.now())
diff --git a/tests/test_pytest_sqlalchemy_mock.py b/tests/test_pytest_sqlalchemy_mock.py
new file mode 100644
index 0000000..c0d2fb0
--- /dev/null
+++ b/tests/test_pytest_sqlalchemy_mock.py
@@ -0,0 +1,27 @@
+from .data import MockData
+from .db import User
+
+
+def test_get_session(session):
+    assert session.execute("SELECT 5").scalar() == 5
+
+
+def test_session_user_table(session):
+    assert session.execute("SELECT count(*) from user").scalar() == 0
+
+
+def test_mocked_session_user_table(mocked_session):
+    user_data = mocked_session.execute("SELECT * from user;").first()
+    raw_data = MockData.USER_DATA[0]
+    assert user_data[0] == raw_data["id"]
+    assert user_data[1] == raw_data["name"]
+    assert user_data[2] == raw_data["surname"]
+    assert user_data[3] == raw_data["is_admin"]
+    assert user_data[4] == raw_data["city"]
+
+
+def test_mocked_session_user_model(mocked_session):
+    user = mocked_session.query(User).filter_by(id=2).first()
+    raw_data = MockData.USER_DATA[1]
+    assert user.name == raw_data["name"]
+    assert user.is_admin == raw_data["is_admin"]