New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed #20456: easier unit-testing for CBV #2368
Conversation
I am ok with the as_instance() technique. Thanks @Keats for this proposal :) In my dreams, calling the class constructor is the obvious way to get an instance of the class... but I know it is not so easy with current CBV implementation... so having some "as_instance()" method looks consistent with "as_view". |
@@ -76,6 +76,19 @@ def view(request, *args, **kwargs): | |||
update_wrapper(view, cls.dispatch, assigned=()) | |||
return view | |||
|
|||
@classonlymethod | |||
def as_instance(cls, request='', *args, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why request=''
by default? Is empty string a suitable default for a request instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about having request
as a required positional argument?
Or, if it is to support calls such as view = views.CustomTemplateView.as_instance()
(no arguments), then what about initializing request like if request is None: request = RequestFactory().get('/fake')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just didn't want to put that logic inside the as_instance, if people need a request they can provide one.
You don't always need a request depending on what you're testing so empty string would be ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't always need a request depending on what you're testing...
Ok.
... so empty string would be ok
I'm not sure about it. If you do not want a default request instance, I'd prefer None
instead of empty string.
That said, both None and empty string would trigger exceptions if an user uses view = CBV().as_instance()
without passing a request, then he calls something that tries to use the request such as view.request.GET
.
May someone else tell his opinion?
Is it possible to call |
You mean MyView.as_view().as_instance() ? That won't work. Do you have an example of a usecase for that ? |
I mean replacing as_view.view implementation (https://github.com/Keats/django/blob/as_instance/django/views/generic/base.py#L63-L68) by a call to as_instance. @classonlymethod
def as_view(cls, **initkwargs):
# ... (unchanged)
def view(request, *args, **kwargs):
self = cls(**initkwargs).as_instance(request, *args, **kwargs)
return self.dispatch(request, *args, **kwargs)
# ... (unchanged) Note: the implementation above does not work in the current pull-request, because in the current pull-request as_instance() is a class-only method... but you get the idea. |
@@ -76,6 +76,19 @@ def view(request, *args, **kwargs): | |||
update_wrapper(view, cls.dispatch, assigned=()) | |||
return view | |||
|
|||
@classonlymethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps not suitable, as mentioned in #2368 (comment).
I quite like @benoitbryon's suggestion. Instead of implementing
Then in your tests you can do At the moment the current patch has a bit too much duplication for my liking. |
I updated the PR @benoitbryon @mjtamlyn |
Looks good to me. It will need documentation - in particular it might be worth documenting that you can pass extra arguments to the
as |
@@ -63,9 +63,7 @@ def view(request, *args, **kwargs): | |||
self = cls(**initkwargs) | |||
if hasattr(self, 'get') and not hasattr(self, 'head'): | |||
self.head = self.get |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it a part of the setup? What about moving it to setup() too?
Added a class method to return an instance of a CBV. This allows to unit test part of a CBV directly, without using the client.
Started changing detail and list tests, will do edit/dates another day |
I did completely forget about that PR, let me know if the current changes are ok and I'll finish it |
I think quite a bit more documentation is needed as Marc suggested. It would be nice to have a small commit that has the basic implementation with some tests and then make refactoring the existing tests a separate commit. See also our patch review checklist. |
Closing in absence of follow-up, feel free to send a new one if you want to continue working on this, thanks! |
This will ease unit testing of views since setup will essentially do everything needed to set the view instance up (other than instantiating it). Credit for idea goes to @Keats based on work in PR django#2368. Still need to do the docs, will be done in next commit.
Added a class method to return an instance of a CBV.
This allows to unit test part of a CBV directly, without using
the client.
Note: as the ticket didn't have much discussion going on, I did a POC (only modified a single test to show how it would work), feedback on it needed before going further