Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

Keats
Copy link

@Keats Keats commented Feb 25, 2014

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

@benoitbryon
Copy link

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):

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?

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')

Copy link
Author

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

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?

@benoitbryon
Copy link

Is it possible to call as_instance() within as_view()?

@Keats
Copy link
Author

Keats commented Mar 5, 2014

You mean MyView.as_view().as_instance() ? That won't work.

Do you have an example of a usecase for that ?

@benoitbryon
Copy link

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.
It may look like this:

    @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

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).

@mjtamlyn
Copy link
Member

mjtamlyn commented Mar 6, 2014

I quite like @benoitbryon's suggestion. Instead of implementing as_instance(), factor out as_view() to look like:

    @classonlymethod
    def as_view(cls, **initkwargs):
        # ... (unchanged)

        def view(request, *args, **kwargs):
            self = cls(**initkwargs).setup(request, *args, **kwargs)
            return self.dispatch(request, *args, **kwargs)

Then in your tests you can do cls(**initkwargs).setup(request, *args, **kwargs).

At the moment the current patch has a bit too much duplication for my liking.

@Keats
Copy link
Author

Keats commented Mar 10, 2014

I updated the PR @benoitbryon @mjtamlyn
If you're ok with that way, I'll update the rest of the file

@mjtamlyn
Copy link
Member

Looks good to me. It will need documentation - in particular it might be worth documenting that you can pass extra arguments to the __init__ and they will get set on the view (if the view already has that attr), for example you can do:

view = MyDetailView(object=instance).setup(request, **kwargs)
view.get_context_data()

as SingleObjectMixin assumes that self.object is set before get_context_data().

@@ -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

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.
@Keats
Copy link
Author

Keats commented Mar 13, 2014

Started changing detail and list tests, will do edit/dates another day

@Keats
Copy link
Author

Keats commented May 19, 2014

I did completely forget about that PR, let me know if the current changes are ok and I'll finish it

@timgraham
Copy link
Member

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.

@mjtamlyn mjtamlyn self-assigned this Dec 24, 2014
@timgraham
Copy link
Member

Closing in absence of follow-up, feel free to send a new one if you want to continue working on this, thanks!

@timgraham timgraham closed this Mar 30, 2015
felipe-lee added a commit to felipe-lee/django that referenced this pull request Oct 10, 2019
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants