Joseph Mancuso
Posted on July 8, 2018
Introduction
One of the reasons controllers are structured like they are is because of how testable they become. In fact, Masonite itself is extremely testable. The main package has a plethora of features, since it houses the entire framework, and is still ~91% code coverage.
Let’s give an example of a typical controller and show how we can modify it to be more testable. I feel like the best way to go through this article is to give a real world example and then how we can modify it to pass tests.
Masonite is very testable but it does allow some ways to do things that are not testable. For example, Masonite comes with builtin helpers which makes various parts of your code not easily tested without mocking a builtin (which is sort of weird).
I’ll be creating some of these articles on how to refactor various controller methods so be sure to give a follow.
Our Example
We’ll keep it very simple here so we can go through all the basics without losing anyone. Here is our example Masonite controller:
class PlanController:
""" Manage User Subscriptions """
def subscribe(self):
plan = request().input('plan_id')
request().user().subscribe(plan, request().input('stripeToken'))
return request().redirect('/plans')
Problem 1
If we go to test this, it might look something like this:
from app.http.controllers.PlanController import PlanController
class TestPlanController:
def setup_method(self):
self.controller = PlanController()
def test_controller_subscribes_user(self):
assert self.controller.show()
This will start throwing a whole bunch of errors because:
- Python testing doesn’t know what the hell a
request()
is because it’s a builtin function that is activated by a Service Provider when the application first boots up. - It’s not correctly able to get the input because we never specified an input.
Refactoring
Let’s refactor the controller so we can at least pass in a request:
from masonite.request import Request
class PlanController:
""" Manage User Subscriptions """
def subscribe(self, request: Request):
plan = request.input('plan_id')
request.user().subscribe(plan, request.input('stripeToken'))
return request.redirect('/plans')
Ok awesome. Now we can at least pass in the request class so let’s go back to our controller test:
from app.http.controllers.PlanController import PlanController
from masonite.request import Request
from masonite.testsuite.TestSuite import generate_wsgi
class TestPlanController:
def setup_method(self):
self.request = Request(generate_wsgi())
self.controller = PlanController()
def test_controller_subscribes_user(self):
assert self.controller.show(self.request)
Perfect! Now we can at least mock the request class. We didn’t really need to mock the request class because we can just use the real class and mock the WSGI request.
Notice though that we imported a generate_wsgi
function into the code from the Masonite test suite. Masonite has a few helper classes and functions used for testing which we can go through in another article.
Problem 2
Ok so this should STILL not work because we didn’t actually load in a user into the request.user()
method. It should always return None
.
Let’s have a request class where we have a user already loaded in. This user will require a subscribe
method and we will also check if the user is subscribed so let’s just mock that up.
We will also mock up a new request class so we can add any methods we need to it in the future:
from app.http.controllers.PlanController import PlanController
from masonite.request import Request
from masonite.testsuite.TestSuite import generate_wsgi
class MockUser:
def subscribe(self, plan, token):
self.plan = plan
def is_subscribed(self, plan):
if self.plan == plan:
return True
return False
class MockRequest(Request):
def user(self):
return MockUser
class TestPlanController:
def setup_method(self):
self.request = MockRequest(generate_wsgi())
self.controller = PlanController()
def test_controller_subscribes_user(self):
assert self.controller.show(self.request)
Awesome. Now we can run the request.user()
method and it will return this MockUser
class.
Problem 3
We do not have the correct inputs now. We can also easily mock those up:
from app.http.controllers.PlanController import PlanController
from masonite.request import Request
from masonite.testsuite.TestSuite import generate_wsgi
...
class TestPlanController:
def setup_method(self):
self.request = MockRequest(generate_wsgi())
self.controller = PlanController()
def test_controller_subscribes_user(self):
# Sets input data
self.request.request_variables = {'plan_id': 'premium', 'stripeToken': 'tok_amex'}
assert self.controller.show(self.request)
GREAT! Now we have everything we need for the test to pass correctly. Now let’s just do 1 more assertion:
...
...
def test_controller_subscribes_user(self):
# Sets input data
self.request.request_variables = {'plan_id': 'premium', 'stripeToken': 'tok_amex'}
assert self.controller.show(self.request)
assert self.request.user().is_subscribed()
We have successfully verified that this controller method successfully subscribes users!
Posted on July 8, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.