Repository: pallets/flask

Issue:
The `template_filter` decorator doesn't work if you don't pass an argument
## What's the issue?

You can use `template_filter` as a decorator, but it only registers the filter if you write an explicit name or an empty set of parentheses. If you call it without parens, the filter doesn't get registered.

It's a small difference and can be confusing.

## Minimal example

Consider the following program:

```python
from flask import Flask, render_template_string


app = Flask(__name__)


@app.template_filter
def double(x):
    return x * 2


@app.route("/")
def index():
    return render_template_string("2 times 2 is {{ 2 | double }}")
```

If you run this app (`flask run --port 8008 --debug`) and then open it in your browser (`http://localhost:8008`) you'll get an error:

```
jinja2.exceptions.TemplateAssertionError: No filter named 'double'.
```

This is confusing, and it took me a while to realise the missing parentheses in `app.template_filter` were at fault.

## Suggested fix

I think it would be helpful if the decorator either:

* Supported being called without parentheses, or
* Printed an explicit warning if called this way, e.g. `Did you use 'template_filter' as a decorator without parentheses? You need to call it with 'template_filter()'`

This is caught by type checkers, but not everybody type checks their Python and the error message is less obvious:

```
Argument 1 to "template_filter" of "App" has incompatible type "Callable[[Any], Any]"; expected "str | None"
```

I've had a look at the relevant code, and I'd be happy to provide a patch if you think this is a useful change.

## Environment

- Python version: Python 3.11.11
- Flask version: Flask 3.1.0



Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 37e777dca8..ee63af477e 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,8 @@ Unreleased
 
 -   Drop support for Python 3.9. :pr:`5730`
 -   Remove previously deprecated code: ``__version__``. :pr:`5648`
+-   ``template_filter``, ``template_test``, and ``template_global`` decorators
+    can be used without parentheses. :issue:`5729`
 
 
 Version 3.1.1
diff --git a/docs/templating.rst b/docs/templating.rst
index 23cfee4cb3..778c960e9d 100644
--- a/docs/templating.rst
+++ b/docs/templating.rst
@@ -137,31 +137,57 @@ using in this block.
 
 .. _registering-filters:
 
-Registering Filters
--------------------
+Registering Filters, Tests, and Globals
+---------------------------------------
 
-If you want to register your own filters in Jinja2 you have two ways to do
-that.  You can either put them by hand into the
-:attr:`~flask.Flask.jinja_env` of the application or use the
-:meth:`~flask.Flask.template_filter` decorator.
+The Flask app and blueprints provide decorators and methods to register your own
+filters, tests, and global functions for use in Jinja templates. They all follow
+the same pattern, so the following examples only discuss filters.
 
-The two following examples work the same and both reverse an object::
+Decorate a function with :meth:`~.Flask.template_filter` to register it as a
+template filter.
 
-    @app.template_filter('reverse')
+.. code-block:: python
+
+    @app.template_filter
+    def reverse(s):
+        return reversed(s)
+
+.. code-block:: jinja
+
+    {% for item in data | reverse %}
+    {% endfor %}
+
+By default it will use the name of the function as the name of the filter, but
+that can be changed by passing a name to the decorator.
+
+.. code-block:: python
+
+    @app.template_filter("reverse")
     def reverse_filter(s):
-        return s[::-1]
+        return reversed(s)
+
+A filter can be registered separately using :meth:`~.Flask.add_template_filter`.
+The name is optional and will use the function name if not given.
+
+.. code-block:: python
 
     def reverse_filter(s):
-        return s[::-1]
-    app.jinja_env.filters['reverse'] = reverse_filter
+        return reversed(s)
 
-In case of the decorator the argument is optional if you want to use the
-function name as name of the filter.  Once registered, you can use the filter
-in your templates in the same way as Jinja2's builtin filters, for example if
-you have a Python list in context called `mylist`::
+    app.add_template_filter(reverse_filter, "reverse")
 
-    {% for x in mylist | reverse %}
-    {% endfor %}
+For template tests, use the :meth:`~.Flask.template_test` decorator or
+:meth:`~.Flask.add_template_test` method. For template global functions, use the
+:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global`
+method.
+
+The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to
+indicate that the registered functions will be avaialble to all templates, not
+only when rendering from within the blueprint.
+
+The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be
+modified directly, as you would when using Jinja outside Flask.
 
 
 Context Processors
diff --git a/src/flask/sansio/app.py b/src/flask/sansio/app.py
index 745fe63639..dcccbce394 100644
--- a/src/flask/sansio/app.py
+++ b/src/flask/sansio/app.py
@@ -660,21 +660,34 @@ def add_url_rule(
                 )
             self.view_functions[endpoint] = view_func
 
-    @setupmethod
+    @t.overload
+    def template_filter(self, name: T_template_filter) -> T_template_filter: ...
+    @t.overload
     def template_filter(
         self, name: str | None = None
-    ) -> t.Callable[[T_template_filter], T_template_filter]:
-        """A decorator that is used to register custom template filter.
-        You can specify a name for the filter, otherwise the function
-        name will be used. Example::
+    ) -> t.Callable[[T_template_filter], T_template_filter]: ...
+    @setupmethod
+    def template_filter(
+        self, name: T_template_filter | str | None = None
+    ) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]:
+        """Decorate a function to register it as a custom Jinja filter. The name
+        is optional. The decorator may be used without parentheses.
+
+        .. code-block:: python
+
+            @app.template_filter("reverse")
+            def reverse_filter(s):
+                return reversed(s)
 
-          @app.template_filter()
-          def reverse(s):
-              return s[::-1]
+        The :meth:`add_template_filter` method may be used to register a
+        function later rather than decorating.
 
-        :param name: the optional name of the filter, otherwise the
-                     function name will be used.
+        :param name: The name to register the filter as. If not given, uses the
+            function's name.
         """
+        if callable(name):
+            self.add_template_filter(name)
+            return name
 
         def decorator(f: T_template_filter) -> T_template_filter:
             self.add_template_filter(f, name=name)
@@ -686,36 +699,52 @@ def decorator(f: T_template_filter) -> T_template_filter:
     def add_template_filter(
         self, f: ft.TemplateFilterCallable, name: str | None = None
     ) -> None:
-        """Register a custom template filter.  Works exactly like the
-        :meth:`template_filter` decorator.
+        """Register a function to use as a custom Jinja filter.
 
-        :param name: the optional name of the filter, otherwise the
-                     function name will be used.
+        The :meth:`template_filter` decorator can be used to register a function
+        by decorating instead.
+
+        :param f: The function to register.
+        :param name: The name to register the filter as. If not given, uses the
+            function's name.
         """
         self.jinja_env.filters[name or f.__name__] = f
 
-    @setupmethod
+    @t.overload
+    def template_test(self, name: T_template_test) -> T_template_test: ...
+    @t.overload
     def template_test(
         self, name: str | None = None
-    ) -> t.Callable[[T_template_test], T_template_test]:
-        """A decorator that is used to register custom template test.
-        You can specify a name for the test, otherwise the function
-        name will be used. Example::
-
-          @app.template_test()
-          def is_prime(n):
-              if n == 2:
-                  return True
-              for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
-                  if n % i == 0:
-                      return False
+    ) -> t.Callable[[T_template_test], T_template_test]: ...
+    @setupmethod
+    def template_test(
+        self, name: T_template_test | str | None = None
+    ) -> T_template_test | t.Callable[[T_template_test], T_template_test]:
+        """Decorate a function to register it as a custom Jinja test. The name
+        is optional. The decorator may be used without parentheses.
+
+        .. code-block:: python
+
+            @app.template_test("prime")
+            def is_prime_test(n):
+                if n == 2:
+                    return True
+                for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
+                    if n % i == 0:
+                        return False
               return True
 
-        .. versionadded:: 0.10
+        The :meth:`add_template_test` method may be used to register a function
+        later rather than decorating.
+
+        :param name: The name to register the filter as. If not given, uses the
+            function's name.
 
-        :param name: the optional name of the test, otherwise the
-                     function name will be used.
+        .. versionadded:: 0.10
         """
+        if callable(name):
+            self.add_template_test(name)
+            return name  # type: ignore[return-value]
 
         def decorator(f: T_template_test) -> T_template_test:
             self.add_template_test(f, name=name)
@@ -727,33 +756,49 @@ def decorator(f: T_template_test) -> T_template_test:
     def add_template_test(
         self, f: ft.TemplateTestCallable, name: str | None = None
     ) -> None:
-        """Register a custom template test.  Works exactly like the
-        :meth:`template_test` decorator.
+        """Register a function to use as a custom Jinja test.
 
-        .. versionadded:: 0.10
+        The :meth:`template_test` decorator can be used to register a function
+        by decorating instead.
 
-        :param name: the optional name of the test, otherwise the
-                     function name will be used.
+        :param f: The function to register.
+        :param name: The name to register the test as. If not given, uses the
+            function's name.
+
+        .. versionadded:: 0.10
         """
         self.jinja_env.tests[name or f.__name__] = f
 
-    @setupmethod
+    @t.overload
+    def template_global(self, name: T_template_global) -> T_template_global: ...
+    @t.overload
     def template_global(
         self, name: str | None = None
-    ) -> t.Callable[[T_template_global], T_template_global]:
-        """A decorator that is used to register a custom template global function.
-        You can specify a name for the global function, otherwise the function
-        name will be used. Example::
+    ) -> t.Callable[[T_template_global], T_template_global]: ...
+    @setupmethod
+    def template_global(
+        self, name: T_template_global | str | None = None
+    ) -> T_template_global | t.Callable[[T_template_global], T_template_global]:
+        """Decorate a function to register it as a custom Jinja global. The name
+        is optional. The decorator may be used without parentheses.
 
-            @app.template_global()
+        .. code-block:: python
+
+            @app.template_global
             def double(n):
                 return 2 * n
 
-        .. versionadded:: 0.10
+        The :meth:`add_template_global` method may be used to register a
+        function later rather than decorating.
 
-        :param name: the optional name of the global function, otherwise the
-                     function name will be used.
+        :param name: The name to register the global as. If not given, uses the
+            function's name.
+
+        .. versionadded:: 0.10
         """
+        if callable(name):
+            self.add_template_global(name)
+            return name
 
         def decorator(f: T_template_global) -> T_template_global:
             self.add_template_global(f, name=name)
@@ -765,13 +810,16 @@ def decorator(f: T_template_global) -> T_template_global:
     def add_template_global(
         self, f: ft.TemplateGlobalCallable, name: str | None = None
     ) -> None:
-        """Register a custom template global function. Works exactly like the
-        :meth:`template_global` decorator.
+        """Register a function to use as a custom Jinja global.
 
-        .. versionadded:: 0.10
+        The :meth:`template_global` decorator can be used to register a function
+        by decorating instead.
+
+        :param f: The function to register.
+        :param name: The name to register the global as. If not given, uses the
+            function's name.
 
-        :param name: the optional name of the global function, otherwise the
-                     function name will be used.
+        .. versionadded:: 0.10
         """
         self.jinja_env.globals[name or f.__name__] = f
 
diff --git a/src/flask/sansio/blueprints.py b/src/flask/sansio/blueprints.py
index 4f912cca05..c8819563a9 100644
--- a/src/flask/sansio/blueprints.py
+++ b/src/flask/sansio/blueprints.py
@@ -440,16 +440,31 @@ def add_url_rule(
             )
         )
 
-    @setupmethod
+    @t.overload
+    def app_template_filter(self, name: T_template_filter) -> T_template_filter: ...
+    @t.overload
     def app_template_filter(
         self, name: str | None = None
-    ) -> t.Callable[[T_template_filter], T_template_filter]:
-        """Register a template filter, available in any template rendered by the
-        application. Equivalent to :meth:`.Flask.template_filter`.
+    ) -> t.Callable[[T_template_filter], T_template_filter]: ...
+    @setupmethod
+    def app_template_filter(
+        self, name: T_template_filter | str | None = None
+    ) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]:
+        """Decorate a function to register it as a custom Jinja filter. The name
+        is optional. The decorator may be used without parentheses.
+
+        The :meth:`add_app_template_filter` method may be used to register a
+        function later rather than decorating.
 
-        :param name: the optional name of the filter, otherwise the
-                     function name will be used.
+        The filter is available in all templates, not only those under this
+        blueprint. Equivalent to :meth:`.Flask.template_filter`.
+
+        :param name: The name to register the filter as. If not given, uses the
+            function's name.
         """
+        if callable(name):
+            self.add_app_template_filter(name)
+            return name
 
         def decorator(f: T_template_filter) -> T_template_filter:
             self.add_app_template_filter(f, name=name)
@@ -461,31 +476,51 @@ def decorator(f: T_template_filter) -> T_template_filter:
     def add_app_template_filter(
         self, f: ft.TemplateFilterCallable, name: str | None = None
     ) -> None:
-        """Register a template filter, available in any template rendered by the
-        application. Works like the :meth:`app_template_filter` decorator. Equivalent to
-        :meth:`.Flask.add_template_filter`.
+        """Register a function to use as a custom Jinja filter.
+
+        The :meth:`app_template_filter` decorator can be used to register a
+        function by decorating instead.
 
-        :param name: the optional name of the filter, otherwise the
-                     function name will be used.
+        The filter is available in all templates, not only those under this
+        blueprint. Equivalent to :meth:`.Flask.add_template_filter`.
+
+        :param f: The function to register.
+        :param name: The name to register the filter as. If not given, uses the
+            function's name.
         """
 
-        def register_template(state: BlueprintSetupState) -> None:
-            state.app.jinja_env.filters[name or f.__name__] = f
+        def register_template_filter(state: BlueprintSetupState) -> None:
+            state.app.add_template_filter(f, name=name)
 
-        self.record_once(register_template)
+        self.record_once(register_template_filter)
 
-    @setupmethod
+    @t.overload
+    def app_template_test(self, name: T_template_test) -> T_template_test: ...
+    @t.overload
     def app_template_test(
         self, name: str | None = None
-    ) -> t.Callable[[T_template_test], T_template_test]:
-        """Register a template test, available in any template rendered by the
-        application. Equivalent to :meth:`.Flask.template_test`.
+    ) -> t.Callable[[T_template_test], T_template_test]: ...
+    @setupmethod
+    def app_template_test(
+        self, name: T_template_test | str | None = None
+    ) -> T_template_test | t.Callable[[T_template_test], T_template_test]:
+        """Decorate a function to register it as a custom Jinja test. The name
+        is optional. The decorator may be used without parentheses.
 
-        .. versionadded:: 0.10
+        The :meth:`add_app_template_test` method may be used to register a
+        function later rather than decorating.
+
+        The test is available in all templates, not only those under this
+        blueprint. Equivalent to :meth:`.Flask.template_test`.
 
-        :param name: the optional name of the test, otherwise the
-                     function name will be used.
+        :param name: The name to register the filter as. If not given, uses the
+            function's name.
+
+        .. versionadded:: 0.10
         """
+        if callable(name):
+            self.add_app_template_test(name)
+            return name  # type: ignore[return-value]
 
         def decorator(f: T_template_test) -> T_template_test:
             self.add_app_template_test(f, name=name)
@@ -497,33 +532,53 @@ def decorator(f: T_template_test) -> T_template_test:
     def add_app_template_test(
         self, f: ft.TemplateTestCallable, name: str | None = None
     ) -> None:
-        """Register a template test, available in any template rendered by the
-        application. Works like the :meth:`app_template_test` decorator. Equivalent to
-        :meth:`.Flask.add_template_test`.
+        """Register a function to use as a custom Jinja test.
 
-        .. versionadded:: 0.10
+        The :meth:`app_template_test` decorator can be used to register a
+        function by decorating instead.
 
-        :param name: the optional name of the test, otherwise the
-                     function name will be used.
+        The test is available in all templates, not only those under this
+        blueprint. Equivalent to :meth:`.Flask.add_template_test`.
+
+        :param f: The function to register.
+        :param name: The name to register the test as. If not given, uses the
+            function's name.
+
+        .. versionadded:: 0.10
         """
 
-        def register_template(state: BlueprintSetupState) -> None:
-            state.app.jinja_env.tests[name or f.__name__] = f
+        def register_template_test(state: BlueprintSetupState) -> None:
+            state.app.add_template_test(f, name=name)
 
-        self.record_once(register_template)
+        self.record_once(register_template_test)
 
-    @setupmethod
+    @t.overload
+    def app_template_global(self, name: T_template_global) -> T_template_global: ...
+    @t.overload
     def app_template_global(
         self, name: str | None = None
-    ) -> t.Callable[[T_template_global], T_template_global]:
-        """Register a template global, available in any template rendered by the
-        application. Equivalent to :meth:`.Flask.template_global`.
+    ) -> t.Callable[[T_template_global], T_template_global]: ...
+    @setupmethod
+    def app_template_global(
+        self, name: T_template_global | str | None = None
+    ) -> T_template_global | t.Callable[[T_template_global], T_template_global]:
+        """Decorate a function to register it as a custom Jinja global. The name
+        is optional. The decorator may be used without parentheses.
 
-        .. versionadded:: 0.10
+        The :meth:`add_app_template_global` method may be used to register a
+        function later rather than decorating.
 
-        :param name: the optional name of the global, otherwise the
-                     function name will be used.
+        The global is available in all templates, not only those under this
+        blueprint. Equivalent to :meth:`.Flask.template_global`.
+
+        :param name: The name to register the global as. If not given, uses the
+            function's name.
+
+        .. versionadded:: 0.10
         """
+        if callable(name):
+            self.add_app_template_global(name)
+            return name
 
         def decorator(f: T_template_global) -> T_template_global:
             self.add_app_template_global(f, name=name)
@@ -535,20 +590,25 @@ def decorator(f: T_template_global) -> T_template_global:
     def add_app_template_global(
         self, f: ft.TemplateGlobalCallable, name: str | None = None
     ) -> None:
-        """Register a template global, available in any template rendered by the
-        application. Works like the :meth:`app_template_global` decorator. Equivalent to
-        :meth:`.Flask.add_template_global`.
+        """Register a function to use as a custom Jinja global.
 
-        .. versionadded:: 0.10
+        The :meth:`app_template_global` decorator can be used to register a function
+        by decorating instead.
 
-        :param name: the optional name of the global, otherwise the
-                     function name will be used.
+        The global is available in all templates, not only those under this
+        blueprint. Equivalent to :meth:`.Flask.add_template_global`.
+
+        :param f: The function to register.
+        :param name: The name to register the global as. If not given, uses the
+            function's name.
+
+        .. versionadded:: 0.10
         """
 
-        def register_template(state: BlueprintSetupState) -> None:
-            state.app.jinja_env.globals[name or f.__name__] = f
+        def register_template_global(state: BlueprintSetupState) -> None:
+            state.app.add_template_global(f, name=name)
 
-        self.record_once(register_template)
+        self.record_once(register_template_global)
 
     @setupmethod
     def before_app_request(self, f: T_before_request) -> T_before_request:


Test patch:
diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py
index e3e2905ab3..ed1683c470 100644
--- a/tests/test_blueprints.py
+++ b/tests/test_blueprints.py
@@ -366,11 +366,35 @@ def test_template_filter(app):
     def my_reverse(s):
         return s[::-1]
 
+    @bp.app_template_filter
+    def my_reverse_2(s):
+        return s[::-1]
+
+    @bp.app_template_filter("my_reverse_custom_name_3")
+    def my_reverse_3(s):
+        return s[::-1]
+
+    @bp.app_template_filter(name="my_reverse_custom_name_4")
+    def my_reverse_4(s):
+        return s[::-1]
+
     app.register_blueprint(bp, url_prefix="/py")
     assert "my_reverse" in app.jinja_env.filters.keys()
     assert app.jinja_env.filters["my_reverse"] == my_reverse
     assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
 
+    assert "my_reverse_2" in app.jinja_env.filters.keys()
+    assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
+    assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
+
+    assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
+    assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
+    assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
+
+    assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
+    assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
+    assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
+
 
 def test_add_template_filter(app):
     bp = flask.Blueprint("bp", __name__)
@@ -502,11 +526,35 @@ def test_template_test(app):
     def is_boolean(value):
         return isinstance(value, bool)
 
+    @bp.app_template_test
+    def boolean_2(value):
+        return isinstance(value, bool)
+
+    @bp.app_template_test("my_boolean_custom_name")
+    def boolean_3(value):
+        return isinstance(value, bool)
+
+    @bp.app_template_test(name="my_boolean_custom_name_2")
+    def boolean_4(value):
+        return isinstance(value, bool)
+
     app.register_blueprint(bp, url_prefix="/py")
     assert "is_boolean" in app.jinja_env.tests.keys()
     assert app.jinja_env.tests["is_boolean"] == is_boolean
     assert app.jinja_env.tests["is_boolean"](False)
 
+    assert "boolean_2" in app.jinja_env.tests.keys()
+    assert app.jinja_env.tests["boolean_2"] == boolean_2
+    assert app.jinja_env.tests["boolean_2"](False)
+
+    assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
+    assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
+    assert app.jinja_env.tests["my_boolean_custom_name"](False)
+
+    assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
+    assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
+    assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
+
 
 def test_add_template_test(app):
     bp = flask.Blueprint("bp", __name__)
@@ -679,6 +727,18 @@ def test_template_global(app):
     def get_answer():
         return 42
 
+    @bp.app_template_global
+    def get_stuff_1():
+        return "get_stuff_1"
+
+    @bp.app_template_global("my_get_stuff_custom_name_2")
+    def get_stuff_2():
+        return "get_stuff_2"
+
+    @bp.app_template_global(name="my_get_stuff_custom_name_3")
+    def get_stuff_3():
+        return "get_stuff_3"
+
     # Make sure the function is not in the jinja_env already
     assert "get_answer" not in app.jinja_env.globals.keys()
     app.register_blueprint(bp)
@@ -688,10 +748,31 @@ def get_answer():
     assert app.jinja_env.globals["get_answer"] is get_answer
     assert app.jinja_env.globals["get_answer"]() == 42
 
+    assert "get_stuff_1" in app.jinja_env.globals.keys()
+    assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
+    assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
+
+    assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
+    assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
+    assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
+
+    assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
+    assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
+    assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
+
     with app.app_context():
         rv = flask.render_template_string("{{ get_answer() }}")
         assert rv == "42"
 
+        rv = flask.render_template_string("{{ get_stuff_1() }}")
+        assert rv == "get_stuff_1"
+
+        rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
+        assert rv == "get_stuff_2"
+
+        rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
+        assert rv == "get_stuff_3"
+
 
 def test_request_processing(app, client):
     bp = flask.Blueprint("bp", __name__)
diff --git a/tests/test_templating.py b/tests/test_templating.py
index c9fb3754a3..85549df094 100644
--- a/tests/test_templating.py
+++ b/tests/test_templating.py
@@ -129,6 +129,30 @@ def my_reverse(s):
     assert app.jinja_env.filters["my_reverse"] == my_reverse
     assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
 
+    @app.template_filter
+    def my_reverse_2(s):
+        return s[::-1]
+
+    assert "my_reverse_2" in app.jinja_env.filters.keys()
+    assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
+    assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
+
+    @app.template_filter("my_reverse_custom_name_3")
+    def my_reverse_3(s):
+        return s[::-1]
+
+    assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
+    assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
+    assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
+
+    @app.template_filter(name="my_reverse_custom_name_4")
+    def my_reverse_4(s):
+        return s[::-1]
+
+    assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
+    assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
+    assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
+
 
 def test_add_template_filter(app):
     def my_reverse(s):
@@ -223,6 +247,30 @@ def boolean(value):
     assert app.jinja_env.tests["boolean"] == boolean
     assert app.jinja_env.tests["boolean"](False)
 
+    @app.template_test
+    def boolean_2(value):
+        return isinstance(value, bool)
+
+    assert "boolean_2" in app.jinja_env.tests.keys()
+    assert app.jinja_env.tests["boolean_2"] == boolean_2
+    assert app.jinja_env.tests["boolean_2"](False)
+
+    @app.template_test("my_boolean_custom_name")
+    def boolean_3(value):
+        return isinstance(value, bool)
+
+    assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
+    assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
+    assert app.jinja_env.tests["my_boolean_custom_name"](False)
+
+    @app.template_test(name="my_boolean_custom_name_2")
+    def boolean_4(value):
+        return isinstance(value, bool)
+
+    assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
+    assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
+    assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
+
 
 def test_add_template_test(app):
     def boolean(value):
@@ -320,6 +368,39 @@ def get_stuff():
     rv = flask.render_template_string("{{ get_stuff() }}")
     assert rv == "42"
 
+    @app.template_global
+    def get_stuff_1():
+        return "get_stuff_1"
+
+    assert "get_stuff_1" in app.jinja_env.globals.keys()
+    assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
+    assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
+
+    rv = flask.render_template_string("{{ get_stuff_1() }}")
+    assert rv == "get_stuff_1"
+
+    @app.template_global("my_get_stuff_custom_name_2")
+    def get_stuff_2():
+        return "get_stuff_2"
+
+    assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
+    assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
+    assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
+
+    rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
+    assert rv == "get_stuff_2"
+
+    @app.template_global(name="my_get_stuff_custom_name_3")
+    def get_stuff_3():
+        return "get_stuff_3"
+
+    assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
+    assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
+    assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
+
+    rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
+    assert rv == "get_stuff_3"
+
 
 def test_custom_template_loader(client):
     class MyFlask(flask.Flask):