Parameterizing tests with pytest - python

I am learning about parameterized tests with pyest. After following the relevant pytest documentation, I came up with this simple example:
import unittest
import pytest
#pytest.fixture(autouse=True, params=['foo', 'bar'])
def foo(request):
print('fixture')
print(request.param)
class Foo(unittest.TestCase):
def setUp(self):
print('unittest setUp()')
def test(self):
print('test')
This gives the following error:
Failed: The requested fixture has no parameter defined for the current test.
E
E Requested fixture 'foo' defined in:
E tests/fixture.py:7
Line 7 is def foo(request):.
What causes this error and how do I fix it?

The goal of fixtures is to pass objects to the test cases, but the fixture you've made doesn't return or yield anything.
Then I'm not sure you can pass objects to a unittest TestCase method, i think it may create some conflicts with the self parameter.
On the other side, it can work with a simple function :
#pytest.fixture(autouse=True, params=['foo', 'bar'])
def foo(request):
print('fixture')
print(request.param)
yield request.param
# class Foo(unittest.TestCase):
# def setUp(self):
# print('unittest setUp()')
#
# def _test(self):
# print('test')
def test_fixture(foo):
assert foo == 'foo'
>>> 1 failed, 1 passed in 0.05 seconds
# test 1 run with foo : OK
# test 2 run with bar : FAILED
EDIT :
Indeed : Why cant unittest.TestCases see my py.test fixtures?

Related

How to Mock Instance Method in Python

Consider the following three files.
# my_class.py
class MyClass:
def __init__(self):
pass
def do_thing(self):
return 5
# main.py
from my_class import MyClass
def my_func():
instance = MyClass()
instance.do_thing()
# test_main.py
from main import my_func
from unittest.mock import patch
#patch('main.MyClass')
def test_my_func(MockMyClass):
my_func()
MockMyClass.do_thing.assert_called_once()
AssertionError: Expected 'do_thing' to have been called once. Called 0 times.
I'm instantiating a class MyClass inside a driver function my_func and calling one of the class's methods do_thing. What I'd like to do is test that when the driver function is invoked, the method of the class is called exactly once. I'm encountering an assertion error that's giving me problems.
I've read a million and one SO posts and other resources online about Python mocks, but I'm not able to figure this out. I thought the trick was that the #patch decorator patches the namespace the module is imported into, not from [Python Mocking a function from an imported module. What am I doing wrong here?
The do_thing method is an instance method of MyClass, NOT class method. You assert MockMyClass.do_thing.assert_called_once() is not correct. Here is the unit test solution:
my_class.py:
class MyClass:
def __init__(self):
pass
def do_thing(self):
return 5
main.py:
from my_class import MyClass
def my_func():
instance = MyClass()
instance.do_thing()
test_main.py:
from main import my_func
import unittest
from unittest.mock import patch
class TestMain(unittest.TestCase):
#patch('main.MyClass')
def test_my_func(self, MockMyClass):
mock_my_class_instance = MockMyClass.return_value
my_func()
mock_my_class_instance.do_thing.assert_called_once()
if __name__ == '__main__':
unittest.main()
unit test results with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------
src/stackoverflow/60539392/main.py 4 0 100%
src/stackoverflow/60539392/my_class.py 5 2 60% 3, 6
src/stackoverflow/60539392/test_main.py 10 0 100%
-----------------------------------------------------------------------
TOTAL

How to use pytest to pass a value in your test using command line args?

I have the following as conftest.py -->
def pytest_addoption(parser):
parser.addoption("--browser")
parser.addoption("--osType", help="Type of operating system")
parser.addoption("--hostURL", action="store", help="prod, stage, or dev")
#pytest.fixture(scope="session")
def browser(request):
return request.config.getoption("--browser")
#pytest.fixture(scope="session")
def osType(request):
return request.config.getoption("--osType")
#pytest.fixture(autouse=True)
def hostURL(request):
return request.config.getoption("--hostURL")
I would like to use the --hostURL flag to pass in value such as prod, stage or dev.
Here's how my test_TheMainTest.py looks -->
import unitest
import pytest
class CheckStatusCodeTest(unittest.TestCase, LoginPage, CustomSeleniumDriver):
def test_CheckStatusCodeOfPages(self, hostURL):
self.login(hostURL)
When I run the above test using pytest -q -s --hostURL prod I get the following error -->
TypeError: test_CheckStatusCodeOfCRPages() missing 1 required positional argument: 'hostURL'
Quoting the docs:
Note
unittest.TestCase methods cannot directly receive fixture arguments as implementing that is likely to inflict on the ability to run general unittest.TestCase test suites.
However, you can still pass regular fixture values to unittest-style tests using autouse fixtures:
class CheckStatusCodeTest(unittest.TestCase):
#pytest.fixture(autouse=True)
def _pass_fixture_value(self, hostURL):
self._hostURL = hostURL
def test_CheckStatusCodeOfPages(self):
assert self._hostURL
You can also check out this answer of mine tackling the same issue for more examples.
Another possibility is to implement an autouse fixture that modifies the test class explicitly. This is useful if you have lots of test classes that should have the identical setup:
#pytest.fixture(scope="class")
def set_fixture_value_to_class(request, hostURL):
request.cls._hostURL = hostURL
#pytest.mark.usefixtures('set_fixture_value_to_class')
class CheckStatusCodeTest(unittest.TestCase):
def test_CheckStatusCodeOfPages(self):
assert self._hostURL
#pytest.mark.usefixtures('set_fixture_value_to_class')
class AnotherTest(unittest.TestCase):
def test_spam(self):
assert self._hostURL
In this case, no need to copy the same fixture to each test class. Just mark all relevant test classes and you're good to go.

Running the same test on two different fixtures

I have a test which currently runs with a single fixture like this:
#pytest.fixture()
def foo():
return 'foo'
def test_something(foo):
# assert something about foo
Now I am creating a slightly different fixture, say
#pytest.fixture
def bar():
return 'bar'
I need to repeat the exact same test against this second fixture. How can I do that without just copy/pasting the test and changing the parameter name?
Beside the test generation, you can do it the "fixture-way" for any number of sub-fixtures applied dynamically. For this, define the actual fixture to be used as a parameter:
#pytest.fixture
def arg(request):
return request.getfuncargvalue(request.param)
The define a test with am indirect parametrization (the param name arg and the fixture name arg must match):
#pytest.mark.parametrize('arg', ['foo', 'bar'], indirect=True)
def test_me(arg):
print(arg)
Lets also define those fixtures we refer to:
#pytest.fixture
def foo():
return 'foo'
#pytest.fixture
def bar():
return 'bar'
Observe how nicely parametrized and identified these tests are:
$ pytest test_me.py -s -v -ra
collected 2 items
test_me.py::test_me[foo] foo
PASSED
test_me.py::test_me[bar] bar
PASSED

Python mock object instantiation

Using Python 2.7, and mock library
How can I test that certain patched object has been initialized with some specific arguments using mock?
Here some sample code and pseudo-code:
unittest.py :
import mock
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
test1 = mock_someobject.return_value
test1 = method_inside_someobject.side_effect = ['something']
mylib.method_to_test()
# How can I assert that method_to_test instanced SomeObject with certain arguments?
# I further test things with that method_inside_someobject call, no problems there...
mylib.py :
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1=val1, arg2=val2, arg3=val3)
obj.method_inside_someobject()
So, how can I test SomeObject was instanced with arg1=val1, arg2=val2, arg3=val3?
If you replaced a class with a mock, creating an instance is just another call. Assert that the right parameters have been passed to that call, for example, with mock.assert_called_with():
mock_someobject.assert_called_with(arg1=val1, arg2=val2, arg3=val3)
To illustrate, I've updated your MCVE to a working example:
test.py:
import mock
import unittest
import mylib
class TestMyLib(unittest.TestCase):
#mock.patch('mylib.SomeObject')
def test_mytest(self, mock_someobject):
mock_instance = mock_someobject.return_value
mock_instance.method_inside_someobject.side_effect = ['something']
retval = mylib.method_to_test()
mock_someobject.assert_called_with(arg1='foo', arg2='bar', arg3='baz')
self.assertEqual(retval, 'something')
if __name__ == '__main__':
unittest.main()
mylib.py:
from someobjectmodule import SomeObject
def method_to_test():
obj = SomeObject(arg1='foo', arg2='bar', arg3='baz')
return obj.method_inside_someobject()
someobjectmodule.py:
class SomeObject(object):
def method_inside_someobject(self):
return 'The real thing'
and running the test:
$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

Python nose setup/teardown class fixture method not executed for test generators

In a hobby project I intend to use nose for testing, I want to put all tests for specific classes into classes since these tests share setup and other functionality. But I can't seem to get nose to execute the setup methods inside the classes.
Here is an example class that is tested:
class mwe():
def __init__(self):
self.example = ""
def setExample(self, ex):
self.example = ex
The tests work, when I don't use classes:
from nose.tools import ok_
import mwe
exampleList = []
def setUp():
print("setup")
exampleList.append("1")
exampleList.append("2")
exampleList.append("3")
def test_Example():
print("test")
for ex in exampleList:
t = mwe.mwe()
t.setExample(ex)
yield check, t, ex
def check(e, ex):
ok_(e.example == ex)
The output is as expected:
setup
test
...
----------------------------------------------------------------------
Ran 3 tests in 0.004s
OK
When a test class is used, the setup method is not executed and therefore no tests are executed.
from nose.tools import ok_
import mwe
class TestexampleClass(object):
def __init__(self):
print("__init__")
self.exampleList = []
def setup(self):
print("setup class")
self.exampleList.append("1")
self.exampleList.append("2")
self.exampleList.append("3")
def test_ExampleClass(self):
print("test class")
for ex in self.exampleList:
t = mwe.mwe()
t.setExample(ex)
yield self.check, t, ex
def check(self, we, ex):
print("check class")
ok_(we.example == ex)
I'm fairly new to python and a newbie with nose, my question is, why is setup not executed? Where is the error in my code?
__init__
test class
----------------------------------------------------------------------
Ran 0 tests in 0.002s
OK
I will be glad for any feedback.
When I use the code from this Question on SO the setup method is executed, as I would expect it.
SOLUTION: After a lot of desperation I found the following:
Nose executes the the class level setup method before the execution of the yielded function, not when the test_* methods are called, as I expected and as is the case for other test_* methods. This obviously goes against the nose documentation:
Setup and teardown functions may be used with test generators. However, please note that setup and teardown attributes attached to the generator function will execute only once. To execute fixtures for each yielded test, attach the setup and teardown attributes to the function that is yielded, or yield a callable object instance with setup and teardown attributes.
Looking at the bug reports, I found the bug report on github.
A possible workaround is to use class level fixtures:
#classmethod
def setup_class(cls):
#do stuff
pass
Your test class needs to extend TestCase and the setup method needs to be called setUp
from unittest import TestCase
class TestUtils(TestCase):
def setUp(self):
self.x = 1
def test_something(self):
self.assertEqual(1, self.x)
which outputs
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

Categories

Resources