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.
Related
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 have view function that uses nmap to scan the devices in the network.
views.py
import nmap
def home(request):
y=nmap.PortScanner()
data = y.scan(hosts="192.168.1.*", arguments="-sP")
context[status]=data['status']['addresses']['ipv4']
return render_template('home.html',context)
Now i want to test this for no devices, 1 device connected and 2 or more device connected. I need to override data in tests.py.
I was thinking that it can be done using mock function. I can override it in tests.py but when simulate responses it not get override in view function.
How can i test this nmap function ?
Monkey patching would be a good solution in your case.
Also have a look at this SO question about monkey patching
here is a possible implementation, of course you need to integrate this into your test framework.
import your_module
class MockPortScanner(object):
# by setting this class member
# before a test case
# you can determine how many result
# should be return from your view
count = 0
def scan(self, *args, **kwargs):
return {
'status': {
'addresses': {
'ipv4': [i for i in range(self.count)]
}
}
}
def your_test_method():
MockPortScanner.count = 5
request = None # create a Mock Request if you need
# here is the mocking
your_module.nmap.PortScanner = MockPortScanner
# call your view as a regular function
rv = your_module.home(request)
# check the response
UPDATE
To have the original PortScanner later in other parts of tests, save it in the tests after importing nmap.
import nmap
OriginalPortScanner = nmap.PortScanner
Then, you will able to select the PortScanner (either original or mock) like:
views.nmap.PortScanner = OriginalPortScanner
#pytest.fixture(scope="function",
params=load_json("path_to_json.json"))
def valid_data(self, request):
return request.param
So thats one fixture in one of my test class. They contain my expected test data. Before each test, i need to modify those json file.
#pytest.fixture(scope="session", autouse=True)
def prepare_file():
// Doing the change and writing it to the json file
But when i run the test, it seem the file are not getting update. But when the test finish. They are updated. What is happening ?
Some things you should understand:
Your fixture scopes definitely need to match if you want to use one inside of the other
Your individual fixtures can access other fixtures if you pass them along
I am not entirely sure if this solves your question, but:
import json
#pytest.fixture(scope="function"):
def output_json_filepath():
return 'path/to/file'
#pytest.fixture(scope="function"):
def json_data(request):
return request.param
#pytest.fixture(scope="function"):
def prepared_data(json_data):
# do something here?
return prepared_data
# Not sure why you need this...
#pytest.fixture(scope="function"):
def dump_data(prepared_data, output_json_filepath):
with io.BytesIO(output_json_filepath, 'wb') as stream:
stream.write(prepared_data)
...
#pytest.mark.unit_test
def some_test(prepared_data):
# use your prepared_data here in your test.
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
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