I've been looking at https://media.readthedocs.org/pdf/graphene-python/latest/graphene-python.pdf for guidance on how to test that my graphql (Graphene, really since I have a python flask app) schema loads correctly. None of the versions for Graphene that I've installed support what the example shows, so I feel like I'm at a loss here.
from graphene.test import Client
def test_hey():
client = Client(my_schema)
executed = client.execute('''{ hey }''', context_value={'user': 'Peter'})
assert executed == {
'data': {
'hey': 'hello Peter!'
}
}
The error that I get: NameError: global name 'Client' is not defined suggests that Client doesn't exist in the graphene realm. Has anyone run into this issue?
You need to ensure that you have graphene available within the scope of your test.
As at this time, graphene.test.Client exists.
Related
I have a flask application which uses a global object data_loader.
The main flask file (let's call it main.py) starts as follow:
app = Flask('my app')
...
data_loader = DataLoader(...)
Later on, this global data_loader object is called in the route methods of the webserver:
class MyClass(Resource):
def get(self):
data_loader.load_some_data()
# ... process data, etc
Using unittest, I want to be able to patch the load_some_data() method. I'm using the flask test_client:
from my_module.main import app
class MyTest(unittest.TestCase):
#classmethod
def setUpClass(cls) -> None:
cls.client = app.test_client('my test client')
How can I patch the data_loader method in subsequent tests in MyTest? I have tried this approach, but it does not work (although the data_loader seems to be replaced at some point):
#unittest.mock.patch('my_module.main.DataLoader')
def my_test(self, DataLoaderMock):
data_loader = DataLoaderMock.return_value
data_loader.my_method.return_value = 'new results (patched)'
with app.test_client() as client:
response = client.get(f'/some/http/get/request/to/MyTest/route',
query_string={...})
# ... some assertions to be tested ...
It seems the data_loader is never truly replaced in the Flask app.
Also, is this considered "good practice" to have a global variable in the Flask server, or is the app supposed to have it stored inside?
Thanks
About mocking, patch.object can be used to modify object attributes:
#unittest.mock.patch.object(data_loader, 'my_method')
def my_test(self, my_method_mock):
my_method_mock.return_value = 'new results (patched)'
with app.test_client() as client:
response = client.get(f'/some/http/get/request/to/MyTest/route',
query_string={...})
my_method_mock.assert_called() # ok!
My solution with interesting insights would be:
import unittest
from unittest.mock import patch
class MyTest(unittest.TestCase):
def test_get(self):
client = app.test_client('my test client')
patcher = patch('{package_here}.{module_here}.DataLoader.load_some_data', return_value={'status': 1})
patcher.start()
self.assertDictEqual(client.get('/').json, {'status': 1})
patcher.stop()
# or
with patch('{package_here}.{module_here}.DataLoader.load_some_data', return_value={'status': 1}):
self.assertDictEqual(client.get('/').json, {'status': 1})
About "good practice" and global variables. Yes, I have seen global variables in various projects. But I don't recommend using global variables. Because:
It can lead to recursive imports and dependency hell. I have worked with large Flask application with recursive imports. It is really pain. And you can't fix all problems for a short time.
Let's imagine you have a tests which mocking a global variables. I think refactoring is more difficult when you have a rather big service.
Separate imports and initialization is really simpler and more configurable. In this case all works in one direction import all dependencies -> load config -> initialization -> run. In other case you will have import -> new instance -> new instance -> import -> ....
Another reason for memory leaks.
Maybe global variables is not bad way for a stand alone packages, modules etc but not for a project. I also want to recommend using some additional tools. This will not only make it easier to write tests, but it will also save you headaches.
I recently started learning flask and created a simple webapp which randomly generates kids' math work sheets in PDF based on user input.
The PDF opens automatically in a browser and can be viewed. But when I try downloading it both on a PC and in Chrome iOS, I get error messages (Chrome PC: Failed - Network error / Chrome iOS:the file could not be downloaded at this time).
You can try it out here: kidsmathsheets.com
I suspect it has something to do with the way I'm generating and returning the PDF file. FYI I'm using ReportLab to generate the PDF. My code below (hosted on pythonanywhere):
from reportlab.lib.pagesizes import A4, letter
from reportlab.pdfgen import canvas
from reportlab.platypus import Table
from flask import Flask, render_template, request, Response
import io
from werkzeug import FileWrapper
# Other code to take in input and generate data
filename=io.BytesIO()
if letter_size:
c = canvas.Canvas(filename, pagesize=letter)
else:
c = canvas.Canvas(filename, pagesize=A4)
pdf_all(c, p_set, answer=answers, letter=letter_size)
c.save()
filename.seek(0)
wrapped_file = FileWrapper(filename)
return Response(wrapped_file, mimetype="application/pdf", direct_passthrough=True)
else:
return render_template('index.html')
Any idea what's causing the issue? Help is much appreciated!
Please check whether you are using an ajax POST request for invoking the endpoint to generate your data and display the PDF respectively. If this is the case - quite probably this causes the behaviour our observe. You might want to try invoking the endpoint with a GET request to /my-endpoint/some-hashed-non-reusable-id-of-my-document where some-hashed-non-reusable-id-of-my-documentwill tell the endpoint which document to serve without allowing users to play around with guesstimates about what other documents you might have. You might try it first like:
#app.route('/display-document/<document_id>'):
def display_document(document_id):
document = get_my_document_from_wherever_it_is(document_id)
binary = get_binary_data_from_document(document)
.........
Prepare response here
.......
return send_file(binary, mimetype="application/pdf")
Kind note: a right click and 'print to pdf' will work but this is not the solution we want
Currently I am investigating using graphene to build my Web server API. I have been using Django-Rest-Framework for quite a while and want to try something different.
I have figured out how to wire it up with my existing project and I can test the query from Graphiql UI, by typing something like
{
industry(id:10) {
name
description
}
}
Now, I want to have the new API covered by Unit/integration tests. And here the problem starts.
All the documentation/post I am checking on testing query/execution on graphene is doing something like
result = schema.execute("{industry(id:10){name, description}}")
assertEqual(result, {"data": {"industry": {"name": "Technology", "description": "blab"}}}
My point is that the query inside execute() is just a big chunk of text and I don't know how I can maintain it in the future. I or other developer in the future has to read that text, figure out what it means and update it if needed.
Is that how this supposed to be? How do you guys write unit test for graphene?
I've been writing tests that do have a big block of text for the query, but I've made it easy to paste in that big block of text from GraphiQL. And I've been using RequestFactory to allow me to send a user along with the query.
from django.test import RequestFactory, TestCase
from graphene.test import Client
def execute_test_client_api_query(api_query, user=None, variable_values=None, **kwargs):
"""
Returns the results of executing a graphQL query using the graphene test client. This is a helper method for our tests
"""
request_factory = RequestFactory()
context_value = request_factory.get('/api/') # or use reverse() on your API endpoint
context_value.user = user
client = Client(schema) # Note: you need to import your schema
executed = client.execute(api_query, context_value=context_value, variable_values=variable_values, **kwargs)
return executed
class APITest(TestCase):
def test_accounts_queries(self):
# This is the test method.
# Let's assume that there's a user object "my_test_user" that was already setup
query = '''
{
user {
id
firstName
}
}
'''
executed = execute_test_client_api_query(query, my_test_user)
data = executed.get('data')
self.assertEqual(data['user']['firstName'], my_test_user.first_name)
...more tests etc. etc.
Everything between the set of ''' s ( { user { id firstName } } ) is just pasted in from GraphiQL, which makes it easier to update as needed. If I make a change that causes a test to fail, I can paste the query from my code into GraphQL, and will often fix the query and paste a new query back into my code. There is purposefully no tabbing on this pasted-in query, to facilitate this repeated pasting.
I have a basic mapping defined in my UrlMappings.groovy If I run my app and go to /api/address/zip-code/12345 I get the show action to respond to the browsers request.
group("/api/address"){
"/zip-code"( resources: 'zipCode', includes: ['show'] )
}
I am trying to create a test to validate that my mapping works because I am going to create a ton of mappings here and I want them to backed by a test. This is my test.
package com.vega.foo
import com.vega.foo.address.ZipCodeController
import grails.test.mixin.Mock
import grails.test.mixin.TestFor
import spock.lang.Specification
import org.codehaus.groovy.grails.web.mapping.UrlMappings
#TestFor(UrlMappings)
#Mock(ZipCodeController)
class UrlMappingsSpec extends Specification {
void "test zip code mapping"() {
expect:
assertForwardUrlMapping("/api/address/zip-code/12345", controller: 'zipCode', action: "show")
}
}
When I try and run this test I get the following error.
Compilation error compiling [unit] tests: BUG! exception in phase
'instruction selection' in source unit
'C:\websites********\test\unit\com\vega\foo\UrlMappingsSpec.groovy'
unexpected NullpointerException
This is about as basic of a URL Mappings test as you can write. What am I missing here?
PR. This is how I was able to pass unit spec.
Changes are almost what I had in comments.
Adding $id to url mapping.
Remove import org.codehaus.groovy.grails.web.mapping.UrlMappings from test.
Specifying the controller and the action explicitly in UrlMapping.
I have written tests for a Django project that i am working on, but one particular fixture fails to load.
The fixture is generated using dumpdata and i havent fiddled with it at all.
I can load the data using manage.py on that fixture without errors. I have verified that the data actually loaded using shell and querying the data.
This is driving me nuts, any help would be much appreciated.
Here is my test file (irrelevant portions removed):
class ViewsFromUrls(TestCase):
fixtures = [
'centers/fixtures/test_data.json',
'intranet/fixtures/test_data.json',
'training/fixtures/test_data.json', #The one that fails to load
]
def setUp(self):
self.c = Client()
self.c.login(username='USER', password='PASS')
...
def test_ViewBatch(self):
b = Batch.objects.all()[0].ticket_number
response = self.c.get(reverse('training.views.view_batch', kwargs={'id':b}))
self.assertTrue(response.status_code, 200)
...
Import the TestCase from django.test:
from django.test import TestCase
class test_something(TestCase):
fixtures = ['one.json', 'two.json']
...
Not: import unittest
Not: import django.utils.unittest
But: import django.test
That's a day of frustration right there.
Stop complaining - it's in the docs :-/
I Am not sure if this fixes your problem, but on this site:
https://code.djangoproject.com/wiki/Fixtures
I found an interesting remark:
you see that Django searches for appnames/fixtures and
settings.FIXTURE_DIRS and loads the first match. So if you use names
like testdata.json for your fixtures you must make sure that no other
active application uses a fixture with the same name. If not, you can
never be sure what fixtures you actually load. Therefore it is
suggested that you prefix your fixtures with the application names,
e.g. myapp/fixtures/myapp_testdata.json .
Applying this (renaming the fixtures with appname as prefix in the filename), solved my problem (I had the same issue as described here)
Check if the fixture is really in the right place. From the docs:
Django will search in three locations
for fixtures:
In the fixtures directory of every installed application
In any directory named in the FIXTURE_DIRS setting
In the literal path named by the fixture
One thing to note, when creating the FIXTURE_DIRS constant in your settings file, be sure to leave out the leading '/' if you have a general fixtures directory off of the root of your project.
Ex:
'/actual/path/to/my/app/fixtures/'
Now, in the settings.py file:
Will NOT work:
FIXTURE_DIRS = '/fixtures/'
Will work:
FIXTURE_DIRS = 'fixtures/'
It's possible this depends on how your other routes are configured, but it was a gotcha that had me scratching my head for a little while. Hope this is useful. Cheers.
A simple mistake I made was adding a custom setUpClass() and forgetting to include super().setUpClass() with it (which of course, is where Django's logic for loading fixtures lives)