0

This question is asked by many but almost all gave the same solution which I am already applying.

So I have classes TestCase,B,C, D & E. Classes C & D inherit class B & class E inherits both C & D. Class B inherits TestCase.

When I run my code, class E is only running with methods for class C and ignoring D all along. Now my classes go like these:

class GenericAuthClass(TestCase):

    def setUp(self):
        """Create a dummy user for the purpose of testing."""
        # set some objects and variabels

    def _get_authenticated_api_client(self):
        pass

class TokenAuthTests(GenericAuthClass):

    def _get_authenticated_api_client(self):
        """Get token recieved on login."""
        super()._get_authenticated_api_client()
        # make object
        return object

class BasicAuthTests(GenericAuthClass):

    def _get_authenticated_api_client(self):
        """Get token recieved on login."""
        super()._get_authenticated_api_client()
        # make object
        return object

class ClientTestCase(BasicAuthTests, TokenAuthTests):
    def dothis(self):
        return self._get_authenticated_api_client()

How can I call method (with same name) in C and D from E like the diamond problem in C++? As of now, when I call the certain method using self.method() from E it only calls that method from C and ignores the same method from D while I think it should call both methods.

Note that the method doesn't exist in class E and my code is working right now without errors but only calling method from C.

This seems like a Python question mainly but tagging Django too as TestCase class might have something to do with it.

halfer
  • 19,824
  • 17
  • 99
  • 186
saadi
  • 646
  • 6
  • 29
  • 1
    Could you provide the full traceback and the line where the error happens? (Probably where you create a `D` `B` or `C` object) – Patrick Haugh Oct 21 '19 at 14:32
  • I have added the complete stack trace. About where I create? I don't create an instance basically `django` does. You can find information about that [here](https://docs.djangoproject.com/en/2.2/topics/testing/overview/#order-in-which-tests-are-executed). Basically all `TestCase` sub-classes are run first. – saadi Oct 21 '19 at 14:37
  • It looks like your `__init__` methods don't match whatever interface `django` expects. You shouldn't need `__init__` methods anyways, as you aren't actually doing anything in them. In your actual code, is there anything in those `__init__` methods other than calls to their parent `__init__` methods? If not, you can remove them and the parent method will be used instead automatically. – Patrick Haugh Oct 21 '19 at 14:41
  • I added those methods while trying to fix a problem, the problem was as I also mentioned in question, in my class `D` I call a certain method which does not exist in `D` but exists in `B` and `C`. When I debug, I find that the method is called for class `B` and code works as if `class D(B)` but not for class `C`. It's multiple inheritance, so it should call it for both of the classes in two different instances. Kind of like diamond problem in `C++`. In short, the problem is, `class D(B,C)` acts like `class D(B)`. – saadi Oct 21 '19 at 14:45
  • That would require `super().method(some, arguments)` in `B.method`. Basically, `D` will go up the MRO (Method Resolution Order) and find the first parent class with a method by that name, and use that method. That method can use `super` to call other methods further up the MRO, but it doesn't happen automatically. – Patrick Haugh Oct 21 '19 at 14:48
  • `D will go up the MRO (Method Resolution Order) and find the first parent class with a method by that name, and use that method.` What if I want `D` to go to `MRO` and instead of stopping at `first parent class`, run that method for all parents one by one? Isn't that the point of multiple inheritance? – saadi Oct 21 '19 at 14:51
  • Does the info in [this question](https://stackoverflow.com/questions/3810410/python-multiple-inheritance-from-different-paths-with-same-method-name) help you? Check the other answers, not the accepted one. it seems like @PatrickHaugh is correct – LampToast Oct 21 '19 at 14:59
  • As always, read [super() considered super](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/). – Daniel Roseman Oct 21 '19 at 15:02
  • Tried using [this](https://stackoverflow.com/a/40187463/10190191) answer but didn't work for me probably because the common base class of `B` and `C`, which is `TestCase` does not have the method I am trying to call and it's a `django` class so I don't think I can add it there. – saadi Oct 21 '19 at 15:11
  • You're not doing anything with the return value of `_get_authenticated_api_client` when you're calling it using `super`. Maybe `GenericAuthClass` can return an unconfigured object that each of the subclasses can modify as necessary before returning to the caller. – Patrick Haugh Oct 21 '19 at 17:50
  • @PatrickHaugh even if I do that, how will it make `ClientTestCase` call `_get_authenticated_api_client` from both parents instead of just one which we are trying to achieve? And I am using that returned value in `ClientTestCase` class in different methods. – saadi Oct 21 '19 at 17:56
  • Add some print statements. I'm pretty sure that `B._get_authenticated_api_client` is calling `C._get_authenticated_api_client`, but because you're not using the result, it isn't changing the output of `B`s method at all. – Patrick Haugh Oct 21 '19 at 18:10

1 Answers1

0

This answer is correct when it says

you have to use something like a common base class to terminate the chain of super calls.

That means that you need to write your own base class that goes between B and C and TestCase that ends the super chain by implementing your additional methods and then not delegating any calls to its parents:

from django.test import TestCase

class MyTestBase(TestCase):
    def method1(self, arg):
        pass
    def method2(self, arg1, arg2):
        pass

class B(MyTestBase):
    def method1(self, arg):
        super().method1(arg)
        ...
    def method2(self, arg1, arg2):
        super().method2(arg1, arg2)
        ...

class C(MyTestBase):
    def method1(self, arg):
        super().method1(arg)
        ...
    def method2(self, arg1, arg2):
        super().method2(arg1, arg2)
        ...

class D(B, C):
    pass
Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • Hi! I followed ur method but the problem remained the same. When I called `self.method1()` in `D`, it called `method1()` from `B` but didn't call `method1` from `C`. While I want both to be called. – saadi Oct 21 '19 at 17:17
  • Could you add the code that you're using to your question? Also, the code in my example incorrectly didn't have `self`, you might double check your code for that. – Patrick Haugh Oct 21 '19 at 17:30