mock_alchemy.mocking¶
A module for basic mocking of SQLAlchemy sessions and calls.
- class mock_alchemy.mocking.AlchemyMagicMock(*args, **kwargs)[source]¶
Bases:
unittest.mock.MagicMockCompares SQLAlchemy expressions for simple asserts.
MagicMock for SQLAlchemy which can compare alchemys expressions in assertions.
For example:
>>> from sqlalchemy import or_ >>> from sqlalchemy.sql.expression import column >>> c = column('column') >>> s = AlchemyMagicMock() >>> _ = s.filter(or_(c == 5, c == 10)) >>> _ = s.filter.assert_called_once_with(or_(c == 5, c == 10)) >>> _ = s.filter.assert_any_call(or_(c == 5, c == 10)) >>> _ = s.filter.assert_has_calls([mock.call(or_(c == 5, c == 10))]) >>> s.reset_mock() >>> _ = s.filter(c == 5) >>> _ = s.filter.assert_called_once_with(c == 10) Traceback (most recent call last): ... AssertionError: expected call not found. Expected: filter(BinaryExpression(sql='"column" = :column_1', params={'column_1': 10})) Actual: filter(BinaryExpression(sql='"column" = :column_1', params={'column_1': 5}))
- __init__(spec: Optional[Any] = ..., side_effect: Optional[Any] = ..., return_value: Any = ..., wraps: Optional[Any] = ..., name: Optional[Any] = ..., spec_set: Optional[Any] = ..., parent: Optional[Any] = ..., _spec_state: Optional[Any] = ..., _new_name: Any = ..., _new_parent: Optional[Any] = ..., **kwargs: Any) None[source]¶
Creates AlchemyMagicMock that can be used as limited SQLAlchemy session.
- _format_mock_call_signature(args, kwargs)[source]¶
Formats the mock call into a string.
- Return type
str
- assert_any_call(*args, **kwargs)[source]¶
Assert for a specific call to have happened.
- Return type
None
- class mock_alchemy.mocking.UnifiedAlchemyMagicMock(*args, **kwargs)[source]¶
Bases:
mock_alchemy.mocking.AlchemyMagicMockA MagicMock that combines SQLALchemy to mock a session.
MagicMock which unifies common SQLALchemy session functions for easier assertions.
- boundary¶
A dict of SQLAlchemy functions or statements that get or retreive data from calls. This dictionary has values that are the callable functions to process the function calls.
- Type
Dict[str, Callable]
- unify¶
A dict of SQLAlchemy functions or statements that are to unifying expressions together. This dictionary has values that are the callable functions to process the function calls. Note that across query calls data and, as such, these calls are not unified. Check out the examples for this class for more detail about this limitation.
- Type
Dict[str, Optional[mock_alchemy.mocking.UnorderedCall]]
- mutate¶
A set of operations that mutate data. The currently supported operations include
.delete(),.add(), and.add_all(). More operations are planned and this is a future area of work.- Type
Set[str]
For example:
>>> from sqlalchemy.sql.expression import column >>> c = column('column') >>> s = UnifiedAlchemyMagicMock() >>> s.query(None).filter(c == 'one').filter(c == 'two').all() [] >>> s.query(None).filter(c == 'three').filter(c == 'four').all() [] >>> s.filter.call_count 2 >>> s.filter.assert_any_call(c == 'one', c == 'two') >>> s.filter.assert_any_call(c == 'three', c == 'four')
In addition, mock data be specified to stub real DB interactions. Result-sets are specified per filtering criteria so that unique data can be returned depending on query/filter/options criteria. Data is given as a list of
(criteria, result)tuples wherecriteriais a list of calls. Reason for passing data as a list vs a dict is that calls and SQLAlchemy expressions are not hashable hence cannot be dict keys.For example:
>>> from sqlalchemy import Column, Integer, String >>> from sqlalchemy.ext.declarative import declarative_base >>> Base = declarative_base() >>> class SomeClass(Base): ... __tablename__ = 'some_table' ... pk1 = Column(Integer, primary_key=True) ... pk2 = Column(Integer, primary_key=True) ... name = Column(String(50)) ... def __repr__(self): ... return str(self.pk1) >>> s = UnifiedAlchemyMagicMock(data=[ ... ( ... [mock.call.query('foo'), ... mock.call.filter(c == 'one', c == 'two')], ... [SomeClass(pk1=1, pk2=1), SomeClass(pk1=2, pk2=2)] ... ), ... ( ... [mock.call.query('foo'), ... mock.call.filter(c == 'one', c == 'two'), ... mock.call.order_by(c)], ... [SomeClass(pk1=2, pk2=2), SomeClass(pk1=1, pk2=1)] ... ), ... ( ... [mock.call.filter(c == 'three')], ... [SomeClass(pk1=3, pk2=3)] ... ), ... ]) # .all() >>> s.query('foo').filter(c == 'one').filter(c == 'two').all() [1, 2] >>> s.query('bar').filter(c == 'one').filter(c == 'two').all() [] >>> s.query('foo').filter(c == 'one').filter(c == 'two').order_by(c).all() [2, 1] >>> s.query('foo').filter(c == 'one').filter(c == 'three').order_by(c).all() [] >>> s.query('foo').filter(c == 'three').all() [3] >>> s.query(None).filter(c == 'four').all() [] # .iter() >>> list(s.query('foo').filter(c == 'two').filter(c == 'one')) [1, 2] # .count() >>> s.query('foo').filter(c == 'two').filter(c == 'one').count() 2 # .first() >>> s.query('foo').filter(c == 'one').filter(c == 'two').first() 1 >>> s.query('bar').filter(c == 'one').filter(c == 'two').first() # .one() >>> s.query('foo').filter(c == 'three').one() 3 >>> s.query('bar').filter(c == 'one').filter(c == 'two').one_or_none() # .get() >>> s.query('foo').get((1, 1)) 1 >>> s.query('foo').get((4, 4)) >>> s.query('foo').filter(c == 'two').filter(c == 'one').get((1, 1)) 1 >>> s.query('foo').filter(c == 'three').get((1, 1)) 1 >>> s.query('foo').filter(c == 'three').get((4, 4)) # dynamic session >>> class Model(Base): ... __tablename__ = 'model_table' ... pk1 = Column(Integer, primary_key=True) ... name = Column(String) ... def __repr__(self): ... return str(self.pk1) >>> s = UnifiedAlchemyMagicMock() >>> s.add(SomeClass(pk1=1, pk2=1)) >>> s.add_all([SomeClass(pk1=2, pk2=2)]) >>> s.add_all([SomeClass(pk1=4, pk2=3)]) >>> s.add_all([Model(pk1=4, name='some_name')]) >>> s.query(SomeClass).all() [1, 2, 4] >>> s.query(SomeClass).get((1, 1)) 1 >>> s.query(SomeClass).get((2, 2)) 2 >>> s.query(SomeClass).get((3, 3)) >>> s.query(SomeClass).filter(c == 'one').all() [1, 2, 4] >>> s.query(SomeClass).get((4, 3)) 4 >>> s.query(SomeClass).get({"pk2": 3, "pk1": 4}) 4 >>> s.query(Model).get(4) 4 # .delete() >>> s = UnifiedAlchemyMagicMock(data=[ ... ( ... [mock.call.query('foo'), ... mock.call.filter(c == 'one', c == 'two')], ... [SomeClass(pk1=1, pk2=1), SomeClass(pk1=2, pk2=2)] ... ), ... ( ... [mock.call.query('foo'), ... mock.call.filter(c == 'one', c == 'two'), ... mock.call.order_by(c)], ... [SomeClass(pk1=2, pk2=2), SomeClass(pk1=1, pk2=1)] ... ), ... ( ... [mock.call.filter(c == 'three')], ... [SomeClass(pk1=3, pk2=3)] ... ), ... ( ... [mock.call.query('foo'), ... mock.call.filter( ... c == 'one', ... c == 'two', ... c == 'three', ... )], ... [ ... SomeClass(pk1=1, pk2=1), ... SomeClass(pk1=2, pk2=2), ... SomeClass(pk1=3, pk2=3), ... ] ... ), ... ]) >>> s.query('foo').filter(c == 'three').all() [3] >>> s.query('foo').all() [] >>> s.query('foo').filter(c == 'three').delete() 1 >>> s.query('foo').filter(c == 'three').all() [] >>> s.query('foo').filter(c == 'one').filter(c == 'two').all() [1, 2] >>> a = s.query('foo').filter(c == 'one').filter(c == 'two') >>> a.filter(c == 'three').all() [1, 2, 3] >>> s = UnifiedAlchemyMagicMock() >>> s.add(SomeClass(pk1=1, pk2=1)) >>> s.add_all([SomeClass(pk1=2, pk2=2)]) >>> s.query(SomeClass).all() [1, 2] >>> s.query(SomeClass).delete() 2 >>> s.query(SomeClass).all() [] >>> s = UnifiedAlchemyMagicMock() >>> s.add_all([SomeClass(pk1=2, pk2=2)]) >>> s.query(SomeClass).delete() 1 >>> s.query(SomeClass).delete() 0
Also note that only within same query functions are unified. After
.all()is called or query is iterated over, future queries are not unified.- __init__(spec: Optional[Any] = ..., side_effect: Optional[Any] = ..., return_value: Any = ..., wraps: Optional[Any] = ..., name: Optional[Any] = ..., spec_set: Optional[Any] = ..., parent: Optional[Any] = ..., _spec_state: Optional[Any] = ..., _new_name: Any = ..., _new_parent: Optional[Any] = ..., **kwargs: Any) None[source]¶
Creates an UnifiedAlchemyMagicMock to mock a SQLAlchemy session.
- _get_previous_call(name, calls)[source]¶
Gets the previous call right before the current call.
- Return type
Optional[_Call]
- class mock_alchemy.mocking.UnorderedCall(value=(), name='', parent=None, two=False, from_kall=True)[source]¶
Bases:
unittest.mock._CallSame as Call except in comparison order of parameters does not matter.
A
mock.Callsubclass that ensures that eqaulity does not depend on order. This isued to check if SQLAlchemy calls match up regardless of order. For example, this is useful in the case of filtering when.filter(y == 4).filter(y == 2)is the same as.filter(y == 2).filter(y == 4).For example:
>>> a = ((1, 2, 3), {'hello': 'world'}) >>> b = ((3, 2, 1), {'hello': 'world'}) >>> UnorderedCall(a) == Call(b) True
- __hash__ = None¶
- class mock_alchemy.mocking.UnorderedTuple(iterable=(), /)[source]¶
Bases:
tupleSame as tuple except in comparison order does not matter.
A tuple in which order does not matter for equality. It compares by remove elements from the other tuple.
For example:
>>> UnorderedTuple((1, 2, 3)) == (3, 2, 1) True
- __hash__ = None¶
- mock_alchemy.mocking.sqlalchemy_call(call, with_name=False, base_call=<class 'unittest.mock._Call'>)[source]¶
Convert
mock.call()into call.Convert
mock.call()into call with all parameters wrapped withExpressionMatcher. This is useful for comparing SQLAlchemy statements for equality.- Parameters
call (
_Call) – The call to convert.with_name (
bool) – Whether to convert the name of the call.base_call (
Any) – The type of call to convert into.
- Return type
Any- Returns
Returns the converted call of the type
base_call.
For example:
>>> args, kwargs = sqlalchemy_call(mock.call(5, foo='bar')) >>> isinstance(args[0], ExpressionMatcher) True >>> isinstance(kwargs['foo'], ExpressionMatcher) True