kaste / mockito-python

Mockito is a spying framework
MIT License
123 stars 12 forks source link

Moking property with execption #26

Closed aaltat closed 4 years ago

aaltat commented 5 years ago

I would like to mock and object attribute and when the attribute is called I would like to raise an exception. I would like to do something like this:

def test(self):
    element = mock()
    when(element).text.thenRaise(ValueError('foo'))

But because when can only mock method, the above does not quite work. I also tried several other ways, but could not get it working. Is there a way to make the mock attribute to raise and exception?

kaste commented 5 years ago

Interesting edge case. Now mocks are always instances of something but properties must be attached to a class.

def test_the_python_way(unstub):
    m = mock()

    def _raise(*a):
        raise ValueError('Boom')

    m.__class__.tx = property(_raise)

    with pytest.raises(ValueError):
        m.tx

def test_property_takes_a_callable(unstub):
    m = mock()

    prop = mock()
    when(prop).__call__(...).thenRaise(ValueError)
    m.__class__.tx = property(prop)

    with pytest.raises(ValueError):
        m.tx

def test_just_fake_the__get__(unstub):
    m = mock()

    prop = mock()
    when(prop).__get__(...).thenRaise(ValueError)
    m.__class__.tx = prop

    with pytest.raises(ValueError):
        m.tx

This is how it works. mockito does not have sugar around this currently.

kaste commented 5 years ago

I could make this work. So the nasty __class__ thing 💥 at least:

def test_d():
    prop = mock()
    when(prop).__get__(...).thenRaise(ValueError)

    m = mock({'tx': prop})
    with pytest.raises(ValueError):
        m.tx
aaltat commented 5 years ago

I did get this one working, with Python unit test framework:

   def test_d(self):
        m = mock()

        def _raise(*a):
            raise ValueError('Boom')

        m.__class__.tx = property(_raise)
        with self.assertRaises(ValueError):
            m.tx

But this enables be to do better testing by mocking Selenium objects, thanks for the support. It might be good idea to document how raise exception with properties.

kaste commented 5 years ago

This feels probably so ugly just because lambda: raise ValueError is a SyntaxError so we have to use this long def _raise(): ... instead.

If you could just property(lambda: raise ValueError()) it would be relatively terse already.

aaltat commented 5 years ago

I agree that having cleaner support would be nicer, although if this would work

prop = mock()
when(prop).__get__(...).thenRaise(ValueError)
m = mock({'tx': prop})

in Python unit test framework, it would be pretty clean.

But lambda can raise an exception with a trick:

    def test_d(self):
        m = mock()
        m.__class__.tx = property(lambda _: exec('raise(ValueError())'))
        with self.assertRaises(ValueError):
            m.tx

Is based on: https://stackoverflow.com/a/9547687

kaste commented 5 years ago

I will very likely support

prop = mock()
when(prop).__get__(...).thenRaise(ValueError)
m = mock({'tx': prop})

in the next version.