I am writing a #pytest.fixture and I need a way to get access to the information of the name of the testcase where the fixture is used.
I just found an article that covers the topic: http://programeveryday.com/post/pytest-creating-and-using-fixtures-for-streamlined-testing/ - Thank you Dan!
import pytest
#pytest.fixture(scope='session')
def my_fixture(request):
print request.function.__name__
# I like the module name, too!
# request.module.__name__
yield
def test_name(my_fixture):
assert False
Problem is it does not work with session scope:
E AttributeError: function not available in session-scoped context
I think that there is no point in have a session scoped fixture, because the #placebo_session (from here) works per function call. So i suggest to simply do it like that:
#pytest.fixture(scope='function')
def placebo_session(request):
session_kwargs = {
'region_name': os.environ.get('AWS_DEFAULT_REGION', 'us-east-1')
}
profile_name = os.environ.get('PLACEBO_PROFILE', None)
if profile_name:
session_kwargs['profile_name'] = profile_name
session = boto3.Session(**session_kwargs)
prefix = request.function.__name__
base_dir = os.environ.get(
"PLACEBO_DIR", os.path.join(os.getcwd(), "placebo"))
record_dir = os.path.join(base_dir, prefix)
if not os.path.exists(record_dir):
os.makedirs(record_dir)
pill = placebo.attach(session, data_path=record_dir)
if os.environ.get('PLACEBO_MODE') == 'record':
pill.record()
else:
pill.playback()
return session
But if you will want to still have something done per session and both per testcase, you can split into two fixtures (and then use func_session fixture).
#pytest.fixture(scope='session')
def session_fixture():
# do something one per session
yield someobj
#pytest.fixture(scope='function')
def func_session(session_fixture, request):
# do something with object created in session_fixture and
# request.function
yield some_val
Related
I am trying to test with my code by mocking the PyGithub library.
I want to create a repository for an organization. So first I need to get it and on the "Organization" returned object, I need to make another call.
It fails when trying to assert that my second method was called.
I am very new to python and I am guessing that there is a missing connection between the mocks but I cannot figure out what.
class GithubService:
def __init__(self, token: str) -> None:
self.__github__ = Github(token)
self.__token__ = token
def create_repo_extra(self, repo_name, description, organization_name, team_name):
try:
organization = self.__github__.get_organization(organization_name)
repo = organization.create_repo(name=repo_name,
description=description,
private=True,
has_issues=False,
has_wiki=False,
has_projects=False,
allow_merge_commit=False)
# do other things with the returned repo.....
return True
except GithubException as ex:
print(ex.data)
return False
Here is the test:
import unittest
from unittest.mock import patch, MagicMock, ANY
from github.Organization import Organization
from github.Repository import Repository
from src.github_service import GithubService
class TestGithubService(unittest.TestCase):
#patch('github.Organization.Organization.create_repo',
side_effect=MagicMock(return_value=Repository(ANY, {}, {}, True)))
#patch('github.MainClass.Github.get_organization',
return_value=MagicMock(return_value=Organization(ANY, {}, {}, True)))
def test_create_repo_returns_true(self, get_organization, create_repo):
sut = GithubService("token")
actual = sut.create_repo_extra('repo-name', 'description', 'organization-name', 'team-name')
get_organization.assert_called() # ok
create_repo.assert_called() # failed
self.assertTrue(actual)
Since you mock your Github.get_organization you can use the MagicMock it returns directly rather than trying to mock another layer.
In this, I patch the same Github.get_organization, but avoid giving it a side effect or return value, and therefore pass it as an arg (like you did).
Then I create a convenience mock_organization and it will be the return value of the patched Github.get_organization.
Finally, the patch is checked like you did, and through the convenience mock_organization I check the create_repo method is called as well.
class TestGithubService(unittest.TestCase):
#patch("github.MainClass.Github.get_organization")
def test_create_repo_returns_true(self, mock_get_organization):
mock_organization = MagicMock()
mock_get_organization.return_value = mock_organization
sut = GithubService("token")
actual = sut.create_repo_extra(
"repo-name", "description", "organization-name", "team-name"
)
mock_get_organization.assert_called() # ok
mock_organization.create_repo.assert_called() # ok
self.assertTrue(actual)
Without seeing more of your code I am not sure why patching Organization did not work, but this is simpler, cleaner and just as effective.
I am having some problems mocking the bucket.objects.filter() method, but I am able to mock most other boto3 calls.
I have a class that is similar to this code in a file with the path my_project.utils.s3_api:
from boto3.session import Session, Config
class S3Resource:
def __init__(self, kwargs):
session = Session()
self.client = session.resource(
's3',
aws_access_key_id=kwargs['access_key'],
aws_secret_access_key=kwargs['secret_key'],
endpoint_url=kwargs['s3_url'],
region_name=kwargs['region'],
config=Config(signature_version='s3v4'
)
self.kwargs = kwargs
<many methods that are already successfully tested here>
# this is the method that I cannot test correctly:
def list_bucket_contents(self):
bucket = self.client.Bucket(self.kwargs['bucket'])
return [summary.key for summary in bucket.objects.filter()]
Then in my tests file I have something like this:
#mock.patch('my_project.utils.s3_api.Session.resource')
def test_list_bucket_contents(self, mock_connection):
ObjectSummary = namedtuple('ObjectSummary', 'bucket_name key')
obj_collection = (
ObjectSummary(bucket_name='mybucket', key='file1.txt'),
ObjectSummary(bucket_name='mybucket', key='file2.txt'),
ObjectSummary(bucket_name='mybucket', key='file3.txt')
)
mock_client = mock.MagicMock()
mock_client.filter.return_value = obj_collection
mock_connection.return_value = mock_client
s3_client = S3Resource(**self.init_args)
s3_client.list_bucket_contents()
print(result)
The returned list is always empty.
The namedtuple part is just an attempt to mimic the bucket.objects.
I would be open to solutions using botocore Stub, but I cannot use a third party library like moto. I just need to mock the call to bucket.objects.filter(). Thank you in advance.
Short answer: replace mock_connection.return_value = mock_client with mock_connection.return_value.Bucket.return_value.objects = mock_client in your test and it would work.
The reason is that self.client is session.resource that you mock, then you create a Bucket (which adds the Bucket.return_value to the mocked path), then you do .objects and only then do you apply the filter() (adds filter.return_value which you already had).
A simple approach that can help you in future cases is to use a helper library I wrote to generate the asserts for you: the mock-generator.
To use it in your case, add mock_autogen.generate_asserts(mock_connection) right after your call to list_bucket_contents, like so:
import mock_autogen
#mock.patch('my_project.utils.s3_api.Session.resource')
def test_list_bucket_contents(self, mock_connection):
ObjectSummary = namedtuple('ObjectSummary', 'bucket_name key')
obj_collection = (
ObjectSummary(bucket_name='mybucket', key='file1.txt'),
ObjectSummary(bucket_name='mybucket', key='file2.txt'),
ObjectSummary(bucket_name='mybucket', key='file3.txt')
)
mock_client = mock.MagicMock()
mock_client.filter.return_value = obj_collection
mock_connection.return_value = mock_client
s3_client = S3Resource(**self.init_args)
s3_client.list_bucket_contents()
mock_autogen.generate_asserts(mock_connection)
It would print all the needed asserts for mock_connection, which would tell you the exact methods used. In your case, the input would contain the following line:
mock_connection.return_value.Bucket.return_value.objects.filter.assert_called_once_with()
From this line you can derive the path you need to mock and replace with the filter objects.
I'm using pytest to run tests, each with a unique account ID. Each test function requires some setup and teardown, and I switched to using fixtures according to previous suggestions. But now I need to use the unique account ID associated with each test to correctly setup and teardown the test. Is there a way to do this?
Also, I have some setup required on the session level and the class level, which is probably unrelated, but needed for the create_and_destroy_test function.
#pytest.fixture(scope="session")
def test_env(request):
test_env = "hello"
return test_env
class TestClass:
#pytest.fixture(scope='class')
def parameters(self, test_env):
print("============Class Level============)")
print("Received test environment, ", test_env)
parameters = [1, 2, 3, 4, 5]
return parameters
#pytest.fixture(scope='function')
def create_and_destroy_test(self, parameters, test_env):
print("============Function Level=============")
print("Posting data") # Needs an account id
print(test_env)
print(parameters)
yield parameters
print("Performing teardown") # Needs an account id
#pytest.mark.parametrize("account_id", [1111, 2222])
def test_one(self, create_and_destroy_test, account_id):
assert 0
#pytest.mark.parametrize("account_id", [3333, 4444])
def test_two(self, create_and_destroy_test, account_id):
assert 0
You can access the test ID or the test name from the request object in your fixture. While there is no direct access to the parameter value, you can just access it by parsing the id or name, as it ends with the parameter value. So you can do something like:
import re
...
#pytest.fixture
def create_and_destroy_test(self, request, parameters, test_env):
match = re.match(r".*\[(.*)\]", request.node.nodeid)
if match:
account_id = match.group(1)
# do something with the ID
Your test id will be something like some_path/test.py::TestClass::test_one[2222], and by using the regex you get the needed parameter from the expression in the brackets, e.g. 2222.
I'm writing tests for a post api, which returns the resource that gets created. But how do I pass this data to a fixture in python so it can cleanup after the test is completed
Cleanup:
#pytest.fixture(scope='function')
def delete_after_post(request):
def cleanup():
// Get ID of resource to cleanup
// Call Delete api with ID to delete the resource
request.addfinalizer(cleanup)
Test:
def test_post(delete_after_post):
Id = post(api)
assert Id
What is the best way to pass the response(ID) back to to the fixture for the cleanup to kick in. Don't want to do the cleanup as part of the test.
You can access that ID using request instance and use anywhere in your code by request.instance.variableName. Like, Suppose your method for deleting id delete(resource_id), here
conftest.py
import pytest
#pytest.fixture(scope='function')
def delete_after_post(request):
def cleanup():
print request.node.resourceId
# Get ID of resource using request.instance.resourceId
# Call Delete api with ID to delete the resource
request.addfinalizer(cleanup)
test file xyz_test.py
def test_post(delete_after_post,request):
request.node.resourceId='3'
I created a fixture that collects cleanup functions for this purpose:
import pytest
#pytest.fixture
def cleaner():
funcs = []
def add_func(func):
funcs.append(func)
yield add_func
for func in funcs:
func()
def test_func(cleaner):
x = 5
cleaner(lambda: print('cleaning', x))
This way you don't need a separate fixture for each use case.
The way I did was create a class called TestRunContext and set static variables to pass around data.
File: test_run_context.py
class TestRunContext:
id_under_test = 0
File: conftest.py
#pytest.fixture(scope='function')
def delete_after_post():
print('hello')
yield
url = 'http://127.0.0.1:5000/api/centres/{0}'.format(TestRunContext.id_under_test)
resp = requests.delete(url)
File: test_post.py
def test_creates_post(delete_after_post):
post_data ={
'name' : 'test',
'address1': 'test',
'city': 'test',
'postcode': 'test',
}
url = 'http://127.0.0.1:5000/api/centres'
data = requests.post(url, post_data)
TestRunContext.id_under_test = data.id
assert data
This works for me for now. But hoping to find a better solution than using ContextManager file. Really dont like this solution.
The following pytest-test uses httpretty, to mock a request. It writes the fetched data to a file:
import requests
import httpretty
import json
from os import listdir
from os.path import join
#httpretty.activate
def test_write_file_from_datasource():
tmpdir = './mytestdir'
# mock the connection
concert_url = 'http://apis.is/concerts'
httpretty.register_uri(httpretty.GET, concert_url,
body = json.dumps({'results': []}),
content_type='application/json')
# fetch data
concerts = requests.get(concert_url).json()
# write data
with open(join(tmpdir, 'concerts.json'), 'w') as json_data:
json.dump(concerts, json_data, indent=2)
assert len(listdir(tmpdir)) == 1
What I would like to do now, is making use of the pytest tmpdir feature. To reach this, I wrote a test like this (imports same as above):
#httpretty.activate
def test_write_file_from_datasource_failing(tmpdir):
tmpdir = str(tmpdir)
# mock the connection
concert_url = 'http://apis.is/concerts'
httpretty.register_uri(httpretty.GET, concert_url,
body = json.dumps({'results': []}),
content_type='application/json')
# fetch data
concerts = requests.get(concert_url).json()
# write data
with open(join(tmpdir, 'concerts.json'), 'w') as json_data:
json.dump(concerts, json_data, indent=2)
assert len(listdir(tmpdir)) == 1
It fails, because the httpretty decorator seems to have problems with the additional parameter:
TypeError: test_write_file_from_datasource_failing() takes exactly 1 argument (0 given)
Any ideas, how to fix this?
It seems that this decorator does not work well with pytest's funcargs.
The only solution I see is to manually call httprertty.enable() and httpretty.disable() methods.
Or create a fixture:
#pytest.yield_fixture
def http_pretty_mock():
httpretty.enable()
yield
httpretty.disable()
def test_write_file_from_datasource_failing(http_pretty_mock, tmpdir):
tmpdir = str(tmpdir)
# mock the connection