I was wondering as to how to build up a world of Domain class objects to use in my unit tests. What´s the best approach?
Say this is my code, ServiceX:
List<Course> getAllCoursesByType(String type) {
List<Course> list = Course.findAllByType(type)
list
}
This is the test for ServiceX:
import grails.buildtestdata.mixin.Build
import grails.test.mixin.TestFor
import grails.test.mixin.Mock
import spock.lang.Specification
#TestFor(ServiceX)
class ServiceXSpec extends Specification {
void "get all open courses"() {
given:
String type = "open"
when:
def list = service.getAllCoursesByType(type)
then:
list.size() == 4
}
}
How can I "pre populate" the test-db (memory) sow that I actually have 4 such objects in the list?
Create integration test for this. See an example here.
This is a judgement call for when to test using a slow integration test. The key is to test your code, not the Grails/hibernate DB code.
Integration tests should not be needed for the bulk of the service testing. I do think you need an integration test for the object interactions in a running system with a real DB. I tend to do that in GUI tests using GEB. These tests usually cover basic end-to-end scenarios. That tests the server side and the GUI interaction with the server.
In the GUI/GEB tests, I don't test all permutations and edge conditions of the service. I do most of that edge testing in unit tests.
I have found that with Grails, if one simple DB action works in an integration test, then most other simple DB actions work. The Grails domain mocks for save(), delete(), etc simulate the 'real' DB actions fairly well. Note: they do operate on objects in memory, so it is not exactly the same.
I don't use Spock, but with JUnit I use this approach (still works with Grails 3):
#TestFor(ServiceX)
#Mock([Course])
class ServiceXTests {
}
#Test
void testXYZ() {
def course= new Course(course: 'ABC')
assert course.save()
. . .
}
This appears to be supported with Spock. I would assume this creation of domain records belongs in the Spock 'given' section. Also, see Grails Testing.
Another great resource is to search the Grails source on Github. I learned a lot from their examples.
It turns out you can add / override methods to the domain classes (for example), something like this:
import grails.buildtestdata.mixin.Build
import grails.test.mixin.TestFor
import grails.test.mixin.Mock
import spock.lang.Specification
import grails.test.mixin.Mock
#Mock([Course])
#TestFor(ServiceX)
class ServiceXSpec extends Specification {
void "get all open courses"() {
given:
String type = "open"
Course.metaclass.static.findAllByType = { String type -> [new Course()]}
when:
def list = service.getAllCoursesByType(type)
then:
list.size() == 1
}
}
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.
Loading spacy models slows down running my unit tests. Is there a way to mock spacy models or Doc objects to speed up unit tests?
Example of a current slow tests
import spacy
nlp = spacy.load("en_core_web_sm")
def test_entities():
text = u"Google is a company."
doc = nlp(text)
assert doc.ents[0].text == u"Google"
Based on the docs my approach is
Constructing the Vocab and Doc manually and setting the entities as tuples.
from spacy.vocab import Vocab
from spacy.tokens import Doc
def test()
alphanum_words = u"Google Facebook are companies".split(" ")
labels = [u"ORG"]
words = alphanum_words + [u"."]
spaces = len(words) * [True]
spaces[-1] = False
spaces[-2] = False
vocab = Vocab(strings=(alphanum_words + labels))
doc = Doc(vocab, words=words, spaces=spaces)
def get_hash(text):
return vocab.strings[text]
entity_tuples = tuple([(get_hash(labels[0]), 0, 1)])
doc.ents = entity_tuples
assert doc.ents[0].text == u"Google"
Is there a cleaner more Pythonic solution for mocking spacy objects for unit tests for entities?
This is a great question actually! I'd say your instinct is definitely right: If all you need is a Doc object in a given state and with given annotations, always create it manually wherever possible. And unless you're explicitly testing a statistical model, avoid loading it in your unit tests. It makes the tests slow, and it introduces too much unnecessary variance. This is also very much in line with the philosophy of unit testing: you want to be writing independent tests for one thing at a time (not one thing plus a bunch of third-party library code plus a statistical model).
Some general tips and ideas:
If possible, always construct a Doc manually. Avoid loading models or Language subclasses.
Unless your application or test specifically needs the doc.text, you do not have to set the spaces. In fact, I leave this out in about 80% of the tests I write, because it really only becomes relevant when you're putting the tokens back together.
If you need to create a lot of Doc objects in your test suite, you could consider using a utility function, similar to the get_doc helper we use in the spaCy test suite. (That function also shows you how the individual annotations are set manually, in case you need it.)
Use (session-scoped) fixtures for the shared objects, like the Vocab. Depending on what you're testing, you might want to explicitly use the English vocab. In the spaCy test suite, we do this by setting up an en_vocab fixture in the conftest.py.
Instead of setting the doc.ents to a list of tuples, you can also make it a list of Span objects. This looks a bit more straightforward, is easier to read, and in spaCy v2.1+, you can also pass a string as a label:
def test_entities(en_vocab):
doc = Doc(en_vocab, words=["Hello", "world"])
doc.ents = [Span(doc, 0, 1, label="ORG")]
assert doc.ents[0].text == "Hello"
If you do need to test a model (e.g. in the test suite that makes sure that your custom models load and run as expected) or a language class like English, put them in a session-scoped fixture. This means that they'll only be loaded once per session instead of once per test. Language classes are lazy-loaded and may also take some time to load, depending on the data they contain. So you only want to do this once.
# Note: You probably don't have to do any of this, unless you're testing your
# own custom models or language classes.
#pytest.fixture(scope="session")
def en_core_web_sm():
return spacy.load("en_core_web_sm")
#pytest.fixture(scope="session")
def en_lang_class():
lang_cls = spacy.util.get_lang_class("en")
return lang_cls()
def test(en_lang_class):
doc = en_lang_class("Hello world")
When writing unit tests in Grails 3.x, we have to mock domains. Here is the example code.
package com.example.service
import grails.test.mixin.Mock
import grails.test.mixin.TestFor
import spock.lang.Ignore
import spock.lang.Specification
#TestFor(SomeService)
#Mock([DomainA, DomainB])
class SomeServiceSpec extends Specification{
...
}
The problem is when a new domain is added, lets say DomainC and the unit tests are dependent upon DomainC, then those unit tests fails. We then have to manually have to add DomainC.
Is there a way to dynamically mock the domains?
#TestFor(SomeService)
#Mock([dynamically mock all domain objects here])
class SomeServiceSpec extends Specification{
...
}
Maybe this is what you need (from grails doc).
Alternatively you can also use the DomainClassUnitTestMixin directly
with the TestMixin annotation and then call the mockDomain method to
mock domains during your test:
#TestFor(BookController)
#TestMixin(DomainClassUnitTestMixin)
class BookControllerSpec extends Specification {
void setupSpec() {
mockDomain(Book)
}
...
Also mockDomains method exists for the list of domains, which you can retrieve by standard way from context.
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.