I need to write tests for the following file.
file1.py
from other import Resource
class _Config:
# ...omit...
#classmethod
def get_config(cls, p1=..., p2=..., p3=..., ...):
return _Config(p1, p2, p3, ..)
def resource(name):
# ....
if name = '...':
return Resource(_Config.get_config())
How to write a pytest fixture for resource?
from ..dir import file1
#pytest.fixture()
def value_resource():
config = file1._Config(....) # how to make the following statement to use config?
return file1.resource('value1')
Related
I've been following those tutorials on (unit) testing for dependency injections and they're using pytest fixtures and I'm trying to replicate something similar in my Flask app. This is what my app looks like:
# all imports
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(modules=[".routes", ".scheduler"])
config = providers.Configuration(yaml_files=["src/conf/config.yaml"])
config.load(envs_required=True)
s3_repository = providers.Resource(
S3Repository, config.get("app.my_service.s3_bucket")
)
my_service = providers.Singleton(
MyService, config, s3_repository
)
My app.py:
container = Container()
container.init_resources()
app = Flask(__name__)
app.container = container
# connect url rules and register error handlers
routes.configure(app)
# schedule and kickoff background jobs
scheduler.schedule(app)
# set flask configuration and logging
app.config.from_mapping(app.container.config.get("app"))
setup_logging(app)
return app
my_service.py
class MyService:
def __init__(self, config: dict, s3_repository: S3Repository) -> None:
self.s3 = s3_repository
self.config = config
# other logic/methods
My S3Repository:
class S3Repository:
def __init__(self, bucket):
self.bucket = bucket
def fetch(self, object_key, columns, filters):
# code to fetch
I'm trying to write my tests and working with pytest for the first time and this is what I have so far:
# TODO - re-write tests for since we're now using dependency injection
import unittest
from unittest.mock import Mock
import pytest as pytest
from src.repository.s3_repository import S3Repository
from src.service.HealthSignalService import HealthSignalService
class TestApp(unittest.TestCase):
def something(self):
pass
#pytest.fixture
def mock_config(mocker):
return mocker.patch("providers.Configuration")
def test_app(mock_config):
from src import create_app
create_app()
When I run this I see:
#pytest.fixture
def mock_config(mocker):
E fixture 'mocker' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, mock_config, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
What am I doing wrong? What am I missing? Right now, I'm following this tutorial - https://docs.pytest.org/en/7.1.x/explanation/fixtures.html
You never define a pytest.fixture with the name of mocker. The arguments passing to the pytest function arguments must be defined by pytest.fixture
for example
#pytest.fixture
def connection():
...
#pytest.fixture
def database(connection):
...
def test_somecase(connection, database):
...
those arguments already defined by the pytest.fixture
In my project I have a Settings class:
config.py
class Settings(BaseSettings):
DEBUG: bool = os.getenv("DEBUG", 'False')
TOKEN_KEY: str = os.getenv("TOKEN_KEY", '')
TOKEN_PASSWORD: str = os.getenv("TOKEN_PASSWORD", '')
#lru_cache()
def get_settings():
return Settings()
And I'm using it in a method like this:
helpers.py
def generate_new_token(user: User) -> str:
settings = get_settings()
private_key = settings.TOKEN_KEY
token_password = settings.TOKEN_PASSWORD
# Do something
I've created these two fixtures:
conftest.py
#pytest.fixture
def get_settings():
return Settings(DEBUG=True, TOKEN_KEY="SOME_FAKE_TOKEN_KEY", TOKEN_PASSWORD='')
#pytest.fixture
def get_user():
return User()
Now I want to test the generate_new_token method with the values returned from fixtures:
test_helpers.py
def test_generate_new_token(get_user, get_settings):
generate_new_token(user=get_user)
In this case, the TOKEN_KEY value from get_settings should be SOME_FAKE_TOKEN_KEY, but it's still empty.
When I debug the code, I can see that it passes the value from get_settings fixture to test_generate_new_token , but then generate_new_token calls the main get_settings method and does not use the get_settings value from fixture as settings value.
I know that if I pass settings as a parameter to generate_new_token like this:
def generate_new_token(user: DexterUser, settings: Settings) -> str:
I can then pass the fixture to it from test function:
def test_generate_new_token(get_user, get_settings):
generate_new_token(user=get_user, settings=get_settings)
But is there anyway to pass the fixture to the main function without having to add it to its parameters?
Your fixture does not replace the function get_settings, it is just another implementation. What you need to do is to patch the implementation with your own, for example:
conftest.py
from unittest import mock
import pytest
#pytest.fixture
def get_settings():
with mock.patch("your_module.helpers.get_settings") as mocked_settings:
mocked_settings.return_value = Settings(
DEBUG=True,
TOKEN_KEY="SOME_FAKE_TOKEN_KEY",
TOKEN_PASSWORD='')
yield
Note that you have to mock the reference of get_settings that is imported in your helpers module, see where to patch.
I'm currently using pytest_addoption to run my API tests, so the tests should run against the environment the user uses on the command line. In my test file, I'm trying to instantiate the UsersSupport class just once, passing the env argument. My code:
conftest.py
import pytest
# Environments
QA1 = 'https://qa1.company.com'
LOCALHOST = 'https://localhost'
def pytest_addoption(parser):
parser.addoption(
'--env',
action='store',
default='qa1'
)
#pytest.fixture(scope='class')
def env(request):
cmd_option = request.config.getoption('env')
if cmd_option == 'qa1':
chosen_env = QA1
elif cmd_option == 'localhost':
chosen_env = LOCALHOST
else:
raise UnboundLocalError('"--env" command line must use "qa1", "localhost"')
return chosen_env
users_support.py
import requests
class UsersSupport:
def __init__(self, env):
self.env = env
self.users_endpoint = '/api/v1/users'
def create_user(self, payload):
response = requests.post(
url=f'{self.env}{self.users_endpoint}',
json=payload,
)
return response
post_create_user_test.py
import pytest
from faker import Faker
from projects import UsersSupport
from projects import users_payload
class TestCreateUser:
#pytest.fixture(autouse=True, scope='class')
def setup_class(self, env):
self.users_support = UsersSupport(env)
self.fake = Faker()
self.create_user_payload = users_payload.create_user_payload
def test_create_user(self):
created_user_res = self.users_support.create_user(
payload=self.create_user_payload
).json()
print(created_user_res)
The issue
When I run pytest projects/tests/post_create_user_test.py --env qa1 I'm getting AttributeError: 'TestCreateUser' object has no attribute 'users_support' error, but if I remove the scope from setup_class method, this method run on every method and not on all methods.
How can I use the env fixture in the setup_class and instantiate the UsersSupport class to use in all methods?
If you use a fixture with class scope, the self parameter does not refer to the class instance. You can, however, still access the class itself by using self.__class__, so you can make class variables from your instance variables.
Your code could look like this:
import pytest
from faker import Faker
from projects import UsersSupport
from projects import users_payload
class TestCreateUser:
#pytest.fixture(autouse=True, scope='class')
def setup_class(self, env):
self.__class__.users_support = UsersSupport(env)
self.__class__.fake = Faker()
self.__class__.create_user_payload = users_payload.create_user_payload
def test_create_user(self):
created_user_res = self.users_support.create_user(
payload=self.create_user_payload
).json() # now you access the class variable
print(created_user_res)
During the test, a new test instance is created for each test.
If you have a default function scoped fixture, it will be called within the same instance of the test, so that the self arguments of the fixture and the current test refer to the same instance.
In the case of a class scoped fixture, the setup code is run in a separate instance before the test instances are created - this instance has to live until the end of all tests to be able to execute teardown code, so it is different to all test instances. As it is still an instance of the same test class, you can store your variables in the test class in this case.
I'm trying to load test data base on the application name from a configuration file.
I'm using ConfigParser which is a nosetest plug in.
Here is my code. I just don't know how to pass the app name while loading the tests on the fly. I tried the constructor, but could not figure out a way to pass parameters to the loadTestsFromTestCase method.
Any ideas?
import unittest
class TestMyApp(unittest.TestCase):
def test1(self):
print "test1: %s" % self.app
def test2(self):
print "test2: %s" % self.app
if __name__ == '__main__':
# here specify the app name
suite = unittest.TestLoader().loadTestsFromTestCase(TestMyApp)
# here specify the other app name
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMyApp))
unittest.TextTestRunner(verbosity=2).run(suite)
You are need a test parametrization! It is easy to done with pytest. Look at this code example:
import pytest
class AppImpl:
def __init__(self, app_name):
self.name = app_name
def get_name(self):
return self.name
#pytest.fixture(params=['App1', 'App2'])
def app(request):
return AppImpl(request.param)
def test_1(app):
assert app.get_name()
def test_2(app):
assert 'App' in app.get_name()
if __name__ == '__main__':
pytest.main(args=[__file__, '-v'])
By using class implementation of you logic AppImpl, you can create a fixture app, which can be parametrized by specific arg params=['App1', 'App2']. Then in your tests test_1 and test_2, use fixture name app in funcargs. This possibility provides more modularity and readability tests.
I'm using google app engine with python and want to run some tests using nosetest.
I want each test to run the same setup function. I have already a lot of tests, so I don't want to go through them all and copy&paste the same function. can I define somewhere one setup function and each test would run it first?
thanks.
You can write your setup function and apply it using the with_setup decorator:
from nose.tools import with_setup
def my_setup():
...
#with_setup(my_setup)
def test_one():
...
#with_setup(my_setup)
def test_two():
...
If you want to use the same setup for several test-cases you can use a similar method.
First you create the setup function, then you apply it to all the TestCases with a decorator:
def my_setup(self):
#do the setup for the test-case
def apply_setup(setup_func):
def wrap(cls):
cls.setup = setup_func
return cls
return wrap
#apply_setup(my_setup)
class MyTestCaseOne(unittest.TestCase):
def test_one(self):
...
def test_two(self):
...
#apply_setup(my_setup)
class MyTestCaseTwo(unittest.TestCase):
def test_one(self):
...
Or another way could be to simply assign your setup:
class MyTestCaseOne(unittest.TestCase):
setup = my_setup