daneah: @momo-sa I almost forgot: please also update `CHANGELOG.md` for this change! I've opened a PR to help us all remember 😄 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b472cb2..2bc541d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+### Fixed
+- Error in calling a dynamic stub endpoint
+
 ## [2.3.0] - 2019-03-25
 ### Added
 - `pytest.ini` for `pytest` configuration
diff --git a/apiron/client.py b/apiron/client.py
index d0259fd..227b031 100644
--- a/apiron/client.py
+++ b/apiron/client.py
@@ -210,7 +210,7 @@ class ServiceCaller:
                 'Stub call for endpoint defined by {}'
                 .format(getattr(endpoint, 'endpoint_params', {}))
             )
-            if hasattr(endpoint.stub_response, '__call__'):
+            if callable(endpoint.stub_response):
                 return endpoint.stub_response(
                     method=method or endpoint.default_method,
                     path_kwargs=path_kwargs,
diff --git a/apiron/endpoint/stub.py b/apiron/endpoint/stub.py
index 89239c0..85fe5fa 100644
--- a/apiron/endpoint/stub.py
+++ b/apiron/endpoint/stub.py
@@ -1,4 +1,7 @@
-class StubEndpoint:
+from apiron.endpoint.endpoint import Endpoint
+
+
+class StubEndpoint(Endpoint):
     """
     A stub endpoint designed to return a pre-baked response
 
@@ -16,16 +19,15 @@ class StubEndpoint:
             Example of a response-determining function::
 
                 def stub_response(**kwargs):
-                    response_map = {
-                        'param_value': {'stub response': 'for param_key=param_value'},
-                        'default': {'default': 'response'},
-                    }
-                    data_key = kwargs['params'].setdefault('param_key', 'default')
-                    return response_map[data_key]
+                    if kwargs.get('params') and kwargs['params'].get('param_key') == 'param_value':
+                        return {'stub response': 'for param_key=param_value'}
+                    else:
+                        return {'default': 'response'}
 
         :param ``**kwargs``:
             Arbitrary parameters that can match the intended real endpoint.
             These don't do anything for the stub but streamline the interface.
         """
-        self.endpoint_params = kwargs if kwargs else {}
+        self.endpoint_params = kwargs or {}
         self.stub_response = stub_response or 'stub for {}'.format(self.endpoint_params)
+        super().__init__(**kwargs)

StubEndpoint not callable
**Describe the bug**
Using the syntax released in v2.3.0 for `StubEndpoint`s raises a `TypeError`.

**To Reproduce**
Steps to reproduce the behavior:

```python
from apiron.endpoint.stub import StubEndpoint
from apiron.service.base import Service

class MyService(Service):
    domain = 'myservice.com'

    my_endpoint = StubEndpoint(stub_response=lambda **kwargs: 'response')

MyService.my_endpoint()
# TypeError: 'StubEndpoint' object is not callable
```

**Expected behavior**
Calling the stub endpoint in this way should call `stub_response`, if it's callable, or return the value of `stub_response`.

**What actually happened**
A `TypeError` 😢 

**Environment:**
 - OS: macOS
 - Python version: 3.4.3
 - `apiron` version: 2.3.0

**Additional context**
This is happening since `StubEndpoint`s do not inherit from `Endpoint` and thus do not receive the metaprogramming treatment other `Endpoint` subclasses do. A fix would be to add the expected behavior described above in `ServiceMeta`.
diff --git a/tests/test_endpoint.py b/tests/test_endpoint.py
index 07b57bd..f37cbaf 100644
--- a/tests/test_endpoint.py
+++ b/tests/test_endpoint.py
@@ -127,6 +127,7 @@ class TestStreamingEndpoint:
 
 
 class TestStubEndpoint:
+
     def test_stub_response(self):
         """
         Test initializing a stub endpoint with a stub response
@@ -150,3 +151,38 @@ class TestStubEndpoint:
             'required_params': {'param_name'},
         }
         assert expected_params == stub_endpoint.endpoint_params
+
+    def test_call_static(self, service):
+        """
+        Test calling a ``StubEndpoint`` with a static response
+        """
+        service.stub_endpoint = endpoint.StubEndpoint(
+            stub_response='stub response',
+        )
+        actual_response = service.stub_endpoint()
+        expected_response = 'stub response'
+        assert actual_response == expected_response
+
+    def test_call_dynamic(self, service):
+        """
+        Test calling a StubEndpoint with a dynamic response
+        """
+        def _test_case(call_kwargs, expected_response):
+            def stub_response(**kwargs):
+                if kwargs.get('params') and kwargs['params'].get('param_key') == 'param_value':
+                    return {'stub response': 'for param_key=param_value'}
+                else:
+                    return {'default': 'response'}
+
+            service.stub_endpoint = endpoint.StubEndpoint(stub_response=stub_response)
+            actual_response = service.stub_endpoint(**call_kwargs)
+            assert actual_response == expected_response
+
+        _test_case(
+            call_kwargs={},
+            expected_response={'default': 'response'},
+        )
+        _test_case(
+            call_kwargs={'params': {'param_key': 'param_value'}},
+            expected_response={'stub response': 'for param_key=param_value'},
+        )

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov sphinx sphinx-autobuild twine wheel
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
-e git+https://github.com/ithaka/apiron.git@ebbc7abfeec309bc64fe1c6bed28fd0e5096ff5f#egg=apiron
Babel==2.14.0
bleach==6.0.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
colorama==0.4.6
coverage==7.2.7
cryptography==44.0.2
docutils==0.19
exceptiongroup==1.2.2
idna==3.10
imagesize==1.4.1
importlib-metadata==6.7.0
importlib-resources==5.12.0
iniconfig==2.0.0
jaraco.classes==3.2.3
jeepney==0.9.0
Jinja2==3.1.6
keyring==24.1.1
livereload==2.7.1
markdown-it-py==2.2.0
MarkupSafe==2.1.5
mdurl==0.1.2
more-itertools==9.1.0
packaging==24.0
pkginfo==1.10.0
pluggy==1.2.0
pycparser==2.21
Pygments==2.17.2
pytest==7.4.4
pytest-cov==4.1.0
pytz==2025.2
readme-renderer==37.3
requests==2.31.0
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==13.8.1
SecretStorage==3.3.3
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-autobuild==2021.3.14
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==2.0.1
tornado==6.2
twine==4.0.2
typing_extensions==4.7.1
urllib3==2.0.7
webencodings==0.5.1
zipp==3.15.0

name: apiron
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - babel==2.14.0
      - bleach==6.0.0
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - colorama==0.4.6
      - coverage==7.2.7
      - cryptography==44.0.2
      - docutils==0.19
      - exceptiongroup==1.2.2
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==6.7.0
      - importlib-resources==5.12.0
      - iniconfig==2.0.0
      - jaraco-classes==3.2.3
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==24.1.1
      - livereload==2.7.1
      - markdown-it-py==2.2.0
      - markupsafe==2.1.5
      - mdurl==0.1.2
      - more-itertools==9.1.0
      - packaging==24.0
      - pkginfo==1.10.0
      - pluggy==1.2.0
      - pycparser==2.21
      - pygments==2.17.2
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytz==2025.2
      - readme-renderer==37.3
      - requests==2.31.0
      - requests-toolbelt==1.0.0
      - rfc3986==2.0.0
      - rich==13.8.1
      - secretstorage==3.3.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-autobuild==2021.3.14
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==2.0.1
      - tornado==6.2
      - twine==4.0.2
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - webencodings==0.5.1
      - zipp==3.15.0
prefix: /opt/conda/envs/apiron

tests/test_endpoint.py::TestStubEndpoint::test_call_static tests/test_endpoint.py::TestStubEndpoint::test_call_dynamic
tests/test_endpoint.py::TestEndpoint::test_call tests/test_endpoint.py::TestEndpoint::test_call_without_service_raises_exception tests/test_endpoint.py::TestEndpoint::test_default_attributes_from_constructor tests/test_endpoint.py::TestEndpoint::test_constructor_stores_passed_attributes tests/test_endpoint.py::TestEndpoint::test_format_response tests/test_endpoint.py::TestEndpoint::test_required_headers tests/test_endpoint.py::TestEndpoint::test_str_method tests/test_endpoint.py::TestEndpoint::test_format_path_with_correct_kwargs tests/test_endpoint.py::TestEndpoint::test_format_path_with_incorrect_kwargs tests/test_endpoint.py::TestEndpoint::test_format_path_with_extra_kwargs tests/test_endpoint.py::TestEndpoint::test_query_parameter_in_path_generates_warning tests/test_endpoint.py::TestEndpoint::test_get_merged_params tests/test_endpoint.py::TestEndpoint::test_get_merged_params_with_unsupplied_param tests/test_endpoint.py::TestEndpoint::test_get_merged_params_with_empty_param tests/test_endpoint.py::TestEndpoint::test_get_merged_params_with_required_and_default_param tests/test_endpoint.py::TestJsonEndpoint::test_format_response_when_unordered tests/test_endpoint.py::TestJsonEndpoint::test_format_response_when_ordered tests/test_endpoint.py::TestJsonEndpoint::test_required_headers tests/test_endpoint.py::TestStreamingEndpoint::test_format_response tests/test_endpoint.py::TestStubEndpoint::test_stub_response tests/test_endpoint.py::TestStubEndpoint::test_extra_params
apiron/client.py apiron/endpoint/stub.py CHANGELOG.md
apiron/client.py apiron/endpoint/stub.py CHANGELOG.md