Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask Unit Testing and not understanding my fix for "TypeError: a bytes-like object is required, not 'str'"

I am currently building a small web application to improve my skills, as part of this, I am trying to go with best practices across the board, testing, CI, well architected, clean code, all of that. Over the last few sessions of working on it, I have been struggling with a test on my root route where instead of returning a string via the route function, I am rendering a template, I have gotten it to work, but I don't understanding why it works, and this bothers me.

Primarily, it's the use of the b, before my assertion string, I assume it is to do with the fact that what I am rendering is not a string, but a html representation, akin to the difference between return and print, but I am hazy and would appreciate for someone to school me.

The line I am asking about is line 4 of the test_homepage_response function. And how it operates. Especially in regards to this error I was getting:

The error being returned:

ERROR: test_home_welcome_return (tests.home_page_tests.HomePageTestClass)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/xibalba/code/reel/tests/home_page_tests.py", line 31, in test_home_welcome_return
    self.assertIn(u"Welcome to Reel!", response.data)
  File "/usr/local/lib/python3.6/unittest/case.py", line 1077, in assertIn
    if member not in container:
TypeError: a bytes-like object is required, not 'str'

My tests for the home route:


# Test Suite
import unittest
from reel import app
from reel.views import home_welcome


class HomePageTesttClass(unittest.TestCase):

    @classmethod
    def setupClass(cls):
        pass

    @classmethod
    def tearDownClass(cls):
        pass

    def setUp(self):
        self.app = app.test_client()
        self.app.testing = True

    def test_homepage_response(self):
        result = self.app.get('/')
        self.assertEqual(result.status_code, 200)
        self.assertIn(b"Welcome to Reel!", result.data)

    def tearDown(self):
        pass

if __name__ == '__main__':
    unittest.main()

My views file:


from reel import app
from flask import render_template


@app.route('/')
def home_welcome():
    return render_template("homepage.html")

@app.route('/signup')
def signup_welcome():
    return 'Welcome to the signup page!'

@app.route('/login')
def login_welcome():
    return 'Welcome to the login page!'

@app.route('/become_a_guide')
def guide_welcome():
    return 'Welcome to the guide page!'

@app.route('/help')
def help_welcome():
    return 'Welcome to the help page!'

Some of the resources I used figuring this out, which pointed me towards the use of the b:

https://github.com/mjhea0/flaskr-tdd#first-test

What does the 'b' character do in front of a string literal?

Appreciate this is a long one, I tried to provide as much context because I honestly feel pretty stupid with this question, but I didn't want to just continue on without knowing why the solution I'm using is working.

Thank you as always.

like image 643
John Von Neumann Avatar asked Jul 22 '17 03:07

John Von Neumann


1 Answers

The very short simple answer, is that string belongs to the str type, while "b" in front of a string will now make it a bytes object, belonging to type bytes. Therefore, the expectation is that yes they should in fact not equal to each other because of the comparison of different types will be expected to fail.

Furthermore, the assertion you are using assertIn, is using the in keyword to test. In order to properly test with in, you need to compare bytes to bytes in this case.

Observe this simple example, that takes you through replicating what you are experiencing:

>>> s = "this is a string"
>>> t = "this is another string"
>>> type(s) == type(t)
True
>>> sb = b"this is a string"
>>> type(sb) == type(s)
False
>>> s in sb
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: a bytes-like object is required, not 'str'

So, as you can see, the "b" actually serves a functional purpose here that gives you a different type of "object".

You could also decode to a string:

>>> res = sb.decode()
>>> type(res)
<class 'str'>

Suggest being explicit about your decoding, however:

>>> res = sb.decode('utf-8')
>>> type(res)
<class 'str'>

Finally, here is an excellent more detailed explanation about the containment test with bytes. Hope this helps.

like image 106
idjaw Avatar answered Oct 18 '22 11:10

idjaw