Search code examples
pythonfactoryfactory-boy

How to create object from another object in Python unit test


I'm having trouble understanding unit testing in Python. I have an object, retailer, which creates another object, deal. deal refers to an attribute created in retailer, so I'm passing it a reference:

class deal():
    def __init__(self, deal_container, parent):

The deal_container attribute also comes from retailer, which calls its own methods to create it. So how do I create everything I need to easily make a deal object?

Do I have to create an instance of retailer in my unit test and then call the method in that object that creates deal?

Can I use FactoryBoy to create an instance of retailer and how do I include the method that creates deal in that object?

What's the best way to approach this?

Here's the unit test. I'm setting up the soup_obj I need to give deal:

class TestExtractString(TestCase):
    fixtures = ['deals_test_data.json']

    def setUp(self):
        with open('/home/danny/PycharmProjects/askarby/deals/tests/BestBuyTest.html', 'r') as myfile:
            text = myfile.read().replace('\n', '')
        self.soup_obj = bs4.BeautifulSoup(text,"html.parser")
        self.deal = self.soup_obj.find_all('div',attrs={'class':'list-item'})[0]

    def test_extracts_title(self):
        z = Retailer.objects.get(pk=1)
        s = dealscan.retailer(z)
        d = dealscan.deal(self.deal,s)
        result = d.extract_string(self.deal,'title')

and here's the relevant bit of the deal class in dealscan. There's a retailer class that creates a deal, but I haven't even written the bit in retailer that creates deal yet. I'm hoping I can mock the bits I need for deal without having to invoke retailer at all, but then how do I deal with the fact that deal references retailer?

class deal():
    def __init__(self, deal_container, parent):
        '''
        Initializes deal object
        Precondition: 0 > price
        Precondition: 0 > old_price
        Precondition: len(currency) = 3

        :param deal_container: obj
        '''
        self.css = self.parent.css

        self.deal_container = deal_container
        self.parent = parent
        self.title = self.extract_string('title')
        self.currency = self.parent.currency
        self.price = self.extract_price('price')
        self.old_price = self.extract_price('old_price')
        self.brand = self.extract_string('brand')
        self.image = self.extract_image('image')
        self.description = self.extract_string('description')

        #define amazon category as clearance_url
        #define all marketplace deals

    def __str__(self):
        return self.title

    def extract_string(self, element, deal):
        '''

        :param object deal: deal object to extract title from
        :param string element: element to look for in CSS
        :return string result: result of string extract from CSS
        '''
        tag = self.css[element]['tag']
        attr = self.css[element]['attr']
        name = self.css[element]['name']
        result = deal.find(tag, attrs={attr: name})
        if result:
            if element == 'title':
                return result.text
            elif element == 'price':
                result = self.extract_price(result).text
                if result:
                    return result
            elif element == 'image':
                result = self.extract_image(result)

        return False

Solution

  • The problem is that the deal object is referencing the parent before it sets the self.parent attribute. Use:

    self.parent = parent        
    self.css = self.parent.css
    self.deal_container = deal_container
    

    and the AttributeError goes away.

    As for the question about whether it's good form to use an object to create another object in a unit test, the answer is that you can use mocks, but it's fine to do it this way. Using a helper method to set up the parent object once in setUp is acceptable and will make the code easier to read, and may improve test performance a little.