{"id":1400,"date":"2021-12-01T12:14:07","date_gmt":"2021-12-01T11:14:07","guid":{"rendered":"https:\/\/www.thomaskeller.biz\/blog\/?p=1400"},"modified":"2021-12-02T16:39:07","modified_gmt":"2021-12-02T15:39:07","slug":"dagger-hilt-learnings","status":"publish","type":"post","link":"https:\/\/www.thomaskeller.biz\/blog\/2021\/12\/01\/dagger-hilt-learnings\/","title":{"rendered":"Dagger Hilt Learnings"},"content":{"rendered":"\n<p>This is a loose list of learnings I had when I first came in contact with <a rel=\"noreferrer noopener\" data-ss1628151310=\"1\" data-ss1638351369=\"1\" href=\"https:\/\/dagger.dev\/hilt\/\" target=\"_blank\">Dagger Hilt<\/a>, especially in regards to testing. So, without further ado, let&#8217;s get into it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Documentation<\/h2>\n\n\n\n<p>While the documentation on Dagger Hilt on developer.android.com <a rel=\"noreferrer noopener\" data-ss1628151310=\"1\" data-ss1628235944=\"1\" href=\"https:\/\/developer.android.com\/training\/dependency-injection\/hilt-android\" target=\"_blank\">is already quite exhaustive<\/a>, I figured I missed a couple of important information and gotchas that I only got from the <a rel=\"noreferrer noopener\" data-ss1628151310=\"1\" data-ss1628235944=\"1\" href=\"https:\/\/dagger.dev\/hilt\/\" target=\"_blank\">official Hilt documentation<\/a>. So be sure you read through <em>both<\/em> thoroughly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scoping<\/h2>\n\n\n\n<p>It&#8217;s buried a bit in the documentation, but it should be remembered that predefined components won&#8217;t mean that all dependencies in the particular component are single instances. Remember that there is a corresponding scope for each and every component type that ensures that there is only <em>one specific<\/em> instance of your thing. This is particularly useful if your thing holds some kind of shared state:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Module\n@InstallIn(ActivityRetainedComponent::class)\n<strong>object<\/strong> RetainedModule {\n    @Provides\n    @ActivityRetainedScope\n    <strong>fun<\/strong> provideFlow() = \n        MutableStateFlow&lt;@JvmSuppressWildcards SomeState&gt;(SomeState.Empty)\n}<\/code><\/pre>\n\n\n\n<p>Communication between different Android <code>ViewModel<\/code> instances come into my mind where this is handy.<\/p>\n\n\n\n<p>Since scoping comes with an overhead, also remember that you can use <code>@Reusable<\/code> in <em>any<\/em> component in case you only want to ensure that there is <em>some<\/em> instance of your otherwise stateless dependency at a time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Module\n@InstallIn(SingletonComponent::class)\n<strong>object<\/strong> SingletonModule {\n    @Provides\n    @Reusable\n    <strong>fun<\/strong> provideHttpClient() = OkHttpClient.Builder()...\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Dagger Hilt Debugging<\/h2>\n\n\n\n<p>Dagger Hilt is &#8211; under the hood &#8211; a beefed up Dagger that comes with a couple of interesting features, like isolated dependency graphs for tests. But after all, it&#8217;s still just Dagger and implemented in Java. Which means your usual rules for making Dagger work apply here (<code>@JvmSuppressWildcards<\/code> for the rescue when dealing with generics, etc.), just with an extra level of complexity that hides the usual unreadable errors.<\/p>\n\n\n\n<p>Since most of my issues resolved around understanding the removal \/ replace of test dependencies, I figured the entry point for Hilt&#8217;s generated test sources is <code>build\/generated\/hilt\/component_sources<\/code>. This directory is split into two parts, one that contains the generated test components for your tests, one for each test class, underneath <code>component_sources\/{variant}UnitTest\/dagger\/hilt\/android\/internal\/testing\/root<\/code> and one that collects an injector implementation, again, for each of your tests, residing in <code>component_sources\/{variant}UnitTest\/package\/path\/to\/your\/tests<\/code>.<\/p>\n\n\n\n<p>The former directory is definitely the more interesting one, because you can check each generated test component if it carries the correct modules you want your test to provide, i.e. you can check if your modules are properly replaced via <code>@TestInstallIn<\/code> or removed via <code>@UninstallModules<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Component Tests have to be Android Tests<\/h2>\n\n\n\n<p>I like to write blackbox component tests on the JVM for REST or DAO implementations. Sometimes this requires a complex dependency setup (mappers, libraries, parsers, &#8230;) where I&#8217;d like to use Dagger to create instances of my subject under test.<\/p>\n\n\n\n<p>Dagger Hilt supports this, kind of, as long as you don&#8217;t care that you rewrite your JUnit 5 component test in JUnit 4 (including all <code>Extension<\/code>s you might have written). Reason is that even though your test doesn&#8217;t need a single Android Framework dependency, you still need to run it with <a rel=\"noreferrer noopener\" data-ss1628235944=\"1\" href=\"http:\/\/robolectric.org\/\" target=\"_blank\">Robolectric<\/a> because this is the only supported way of using Hilt in JVM tests as of now:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><em>Even though we have plans in the future for Hilt without Android, right now Android is a requirement so it isn&#8217;t possible to run the Hilt Dagger graph without either an instrumentation test or Robolectric.<\/em><\/p><cite>Eric Chang<\/cite><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">UI Testing : Activity<\/h2>\n\n\n\n<p>Using Dagger Hilt for an Activity test is straight forward, you basically follow the documentation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@HiltAndroidTest\n@RunWith(RobolectricTestRunner::class)\n@Config(application = HiltTestApplication::class)\n<strong>internal class <\/strong>SomeActivityTest {\n    \n    @get:Rule(order = 0)\n    <strong>val<\/strong> hiltRule = HiltAndroidRule(this)\n    \n    @get:Rule(order = 1)\n    <strong>val<\/strong> activityScenarioRule = ActivityScenarioRule(SomeActivity::class)\n    \n    @Inject <strong>lateinit var<\/strong> dep: SomeDep\n    \n    @Before\n    <strong>fun<\/strong> init() {\n        hiltRule.inject()\n    }\n    \n    @Test\n    <strong>fun<\/strong> someTest() {\n         \/\/ stub dep\n         ...\n         \/\/ launch\n         activityScenarioRule.launchActivity()\n    }\n}<\/code><\/pre>\n\n\n\n<p>This works nicely in case your dependency is in <code>Singleton<\/code> scope, because your test instance itself cannot inject anything else but <code>Singleton<\/code>-scoped dependencies, but what if not and we have to stub the aforementioend <code>MutableStateFlow<\/code>?<\/p>\n\n\n\n<p>Now, Hilt has a concept called <code>EntryPoint<\/code>s that we can define in a test-local manner. The entry point then targets a specific component and can fetch dependencies from that. To find the right component for your entry point it helps looking at the <a rel=\"noreferrer noopener\" data-ss1628235944=\"1\" href=\"https:\/\/dagger.dev\/hilt\/components\" target=\"_blank\">component hiearchy<\/a>. If our dependency lives in the <code>ActivityRetainedComponent<\/code>, its as easy as creating a new entry point into this for our test, right?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    ...\n    \n    @get:Rule(order = 0)\n    <strong>val<\/strong> hiltRule = HiltAndroidRule(this)\n    \n    @EntryPoint\n    @InstallIn(ActivityRetainedComponent::class)\n    <strong>internal interface <\/strong>ActivityRetainedEntryPoint {\n       <strong>val flow<\/strong>: MutableStateFlow&lt;@JvmSuppressWildcards SomeState&gt;\n    }\n    \n    @Before\n    <strong>fun<\/strong> init() {\n        <strong>hiltRule<\/strong>.inject()\n    }\n    ...<\/code><\/pre>\n\n\n\n<p>Wrong. To get an instance of the entry point, you have to call <code>EntryPoints.get(component, ActivityRetainedEntryPoint::class)<\/code>, where the <code>component<\/code> is the instance of the thing the component is owned, i.e. an <code>Application<\/code> instance for <code>SingletonComponent<\/code> entry points, an <code>Activity<\/code> instance for <code>ActivityComponent<\/code> entry points, aso. But what is the thing that owns the <code>ActivityRetainedComponent<\/code> and where to we get access to it?<\/p>\n\n\n\n<p>Turns out we don&#8217;t need it. Looking at the <a rel=\"noreferrer noopener\" data-ss1628235944=\"1\" href=\"https:\/\/dagger.dev\/hilt\/components\" target=\"_blank\">component hiearchy<\/a> again we see that <code>ActivityComponent<\/code>,  <code>FragmentComponent<\/code> and a few others are direct or indirect child components of the <code>ActivityRetainedComponent<\/code> and therefor see all of it&#8217;s dependencies. So we &#8220;just&#8221; need an <code>Activity<\/code> or <code>Fragment<\/code> instance to get our dependency.<\/p>\n\n\n\n<p>The <a rel=\"noreferrer noopener\" data-ss1628235944=\"1\" href=\"https:\/\/dagger.dev\/hilt\/testing#accessing-activitycomponent-bindings\" target=\"_blank\">Hilt docs state<\/a> that the easiest way is to define a custom static activity class in your code, like this<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@AndroidEntryPoint\n<strong>class<\/strong> TestActivity : AppCompatActivity() {\n    <strong>val<\/strong> flow: MutableStateFlow&lt;SomeState&gt;\n}<\/code><\/pre>\n\n\n\n<p>but that Activity needs to go through the lifecycle at first to be usable. Can&#8217;t we just use the <code>Activity<\/code> instance we launch anyways for this? Turns out we can, we just need to &#8220;extract&#8221; the actual <code>Activity<\/code> instance from the <code>ActivityScenario<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>fun <\/strong>&lt;T : Activity&gt; ActivityScenario&lt;T&gt;.getActivity(): T? {\n    <strong>val <\/strong>field = <strong>this<\/strong>::<strong>class<\/strong>.<em>java<\/em>.getDeclaredField(<strong>\"currentActivity\"<\/strong>)\n    field.<em>isAccessible <\/em>= <strong>true\n    <\/strong>@Suppress(<strong>\"UNCHECKED_CAST\"<\/strong>)\n    <strong>return <\/strong>field.get(<strong>this<\/strong>) <strong>as? <\/strong>T?\n}\n\n<strong>inline fun <\/strong>&lt;<strong>reified <\/strong>E : Any&gt; ActivityScenarioRule&lt;*&gt;.getActivityEntryPoint(): E =\n    EntryPoints.get(\n        <strong>getScenario(<\/strong>).<em>getActivity<\/em>() ?: <em>error<\/em>(<strong>\"activity not started\"<\/strong>),\n        E::<strong>class<\/strong>.<em>java\n    <\/em>)<\/code><\/pre>\n\n\n\n<p>so our complete test looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@HiltAndroidTest\n@RunWith(RobolectricTestRunner::class)\n@Config(application = HiltTestApplication::class)\n<strong>internal<\/strong> <strong>class<\/strong> SomeActivityTest {\n    \n    @get:Rule(order = 0)\n    <strong>val<\/strong> hiltRule = HiltAndroidRule(this)\n    \n    @get:Rule(order = 1)\n   <strong> val <\/strong>activityScenarioRule<strong> <\/strong>= ActivityScenarioRule(SomeActivity::<strong>class<\/strong>)\n    \n    @EntryPoint\n    @InstallIn(ActivityComponent::class)\n    <strong>internal interface <\/strong>EntryPoint {\n       <strong>val <\/strong>flow: MutableStateFlow&lt;SomeState&gt;\n    }\n    \n    @Before\n    <strong>fun<\/strong> init() {\n        <strong>hiltRule<\/strong>.inject()\n    }\n    \n    @Test\n    <strong>fun<\/strong> someTest() {\n         \/\/ launch\n         <strong>activityScenarioRule<\/strong>.launchActivity()\n         \/\/ get the flow and do things with it\n         <strong>val<\/strong> flow = <strong>activityScenarioRule<\/strong>.getActivityEntryPoint&lt;EntryPoint&gt;().flow\n    }        \n}<\/code><\/pre>\n\n\n\n<p>Downside is now, of course, that the <code>Activity<\/code> must be launched (started even!) before one gets access to the dependency. Can we fix that? Unfortunately not without moving the Dependency up the component hierarchy and installing the original module that provided it. See <strong>Replacing Ad-hoc Dependencies<\/strong> for a way to do that.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">UI Testing : Fragments<\/h2>\n\n\n\n<p>The first issue with Hilt-enabled Fragment testing is that <a rel=\"noreferrer noopener\" data-ss1638351369=\"1\" href=\"https:\/\/issuetracker.google.com\/issues\/159104191\" target=\"_blank\">there is no support for Hilt-enabled Fragment testing<\/a>. The problem is that the regular <code>androidx.fragment:fragment-testing<\/code> artifact comes with an internal <code>TestActivity<\/code> that is not Hilt-enabled, so we have to write our own:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@AndroidEntryPoint(AppCompatActivity::class)\n<strong>class<\/strong> TestHiltActivity : Hilt_TestHiltActivity() {\n    <strong>override fun <\/strong>onCreate(savedInstanceState: Bundle?) {\n        val themeRes = intent.getIntExtra(THEME_EXTRAS_BUNDLE_KEY, 0)\n        require(themeRes != 0) { \"No theme configured for ${this.javaClass}\" }\n        setTheme(themeRes)\n        super.onCreate(savedInstanceState)\n    }\n\n    <strong>companion object <\/strong>{\n        <strong>private const val<\/strong> THEME_EXTRAS_BUNDLE_KEY = \"theme-extra-bundle-key\"\n\n        <strong>fun<\/strong> createIntent(context: Context, @StyleRes themeResId: Int): Intent {\n            <strong>val<\/strong> componentName = ComponentName(context, TestHiltActivity::class.java)\n            <strong>return<\/strong> Intent.makeMainActivity(componentName)\n                .putExtra(THEME_EXTRAS_BUNDLE_KEY, themeResId)\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p>This is basically copied from the original <code>TestActivity<\/code> and adapted. I place this into a separate Gradle module, because like the original artifact, this has to become a <code>debugImplementation<\/code> dependency.<\/p>\n\n\n\n<p>Now we need a separate <code>FragmentScenario<\/code> and <code>FragmentScenarioRule<\/code> as well, to use this new Activity. I&#8217;ll not paste the complete implementation for them here, but <a rel=\"noreferrer noopener\" data-ss1638351369=\"1\" href=\"https:\/\/gist.github.com\/realdadfish\/83ecbc9157a6bfeb0c7da137278898f5\" target=\"_blank\">refer you to this gist<\/a> where I collected them.<\/p>\n\n\n\n<p>With <code>FragmentScenario<\/code> we have more control over the <code>Fragment<\/code>s state in which it is launched. My implementation by default launches a <code>Fragment<\/code> in <code>Lifecycle.State.INITIALIZED<\/code>, basically the state in which the <code>Fragment<\/code> is right after it&#8217;s instantiation and &#8211; more importantly &#8211; <em>after<\/em> Hilt injected its dependencies!<\/p>\n\n\n\n<p>So, we can now stub dependencies that are used during <code>onCreate<\/code> like so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@EntryPoint\n@InstallIn(FragmentComponent::class)\n<strong>internal interface <\/strong>FragmentEntryPoint {\n    val someFlow: MutableStateFlow&lt;SomeState&gt;\n}\n\n<strong>private val <\/strong>fragmentScenarioRule = HiltFragmentScenarioRule(SomeFragment::class)\n<strong>private val<\/strong> entryPoint by lazy {\n    fragmentScenarioRule.getFragmentEntryPoint&lt;FragmentEntryPoint&gt;()\n}\n\n...\n<strong>val<\/strong> fragmentScenario = fragmentScenarioRule.launchFragment(R.style.AppTheme)\nentryPoint.someFlow.tryEmit(SomeState.SomeValue)               \nfragmentScenario.moveToState(Lifecycle.State.RESUMED)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Replacing Ad-hoc Dependencies<\/h2>\n\n\n\n<p>There are times where you don&#8217;t provision dependencies through specific modules that you could, on the test side of things, replace via <code>@TestInstallIn<\/code> or alike. A good example for this are <code>UseCase<\/code> classes.<\/p>\n\n\n\n<p>I tend to test my View (Fragment or Activity)  together with my Android ViewModel implementation and the latter makes use of these <code>UseCase<\/code> classes to interface to my domain layer. Naturally one wants to replace the <code>UseCase<\/code> implementation with a fake implementation or a mock, but how can one accomplish this with Hilt?<\/p>\n\n\n\n<p>Turns out it&#8217;s quite easy &#8211; all you have to do is to <code>@BindValue<\/code> your dependency in your test class. A dependency provisioned through this seems to take precendence over constructor-injected ad-hoc dependencies:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@HiltAndroidTest\n@RunWith(RobolectricTestRunner::class)\n@Config(application = HiltTestApplication::class)\n<strong>internal class<\/strong> SomeActivityTest {\n\n    @get:Rule(order = 0)\n    <strong>val<\/strong> hiltRule = HiltAndroidRule(this)\n\n    @get:Rule(order = 1)\n    <strong>val<\/strong> activityScenarioRule = ActivityScenarioRule(SomeActivity::class)\n\n    @BindValue\n    <strong>val<\/strong> useCase: MyUseCase = mockk()\n\n    @Before\n    <strong>fun<\/strong> init() {\n        hiltRule.inject()\n    }\n    ...\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Lifecycle and Scoping in Tests<\/h2>\n\n\n\n<p>More often than not you might stumble in weird test issues when you follow the &#8220;good citizen&#8221; rule and provision even your test dependencies (e.g. mocks) with <code>@Reusable<\/code>. In some cases you might end up with two different instances, one in your test and one in your production code.<\/p>\n\n\n\n<p>So, spare yourself a few headaches and and just always annotate those test dependencies with the scope matching the component, e.g. <code>@Singleton<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Module Size<\/h2>\n\n\n\n<p>The ability to uninstall certain modules per test has the nice &#8220;side-effect&#8221; of training yourself to make your modules smaller, because the larger a module is &#8211; and the more unrelated dependencies it provisions, the more work you have to do to provide the &#8220;other&#8221; dependencies you&#8217;re not interested in once you uninstall that module for a particular test case.<\/p>\n\n\n\n<p>Well, at least Dagger tells you that something is missing, by printing out it&#8217;s beloved compilation errors, right?!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Global Test Modules<\/h2>\n\n\n\n<p>Sometimes you want to remove some dependency from your graph that would otherwise go havoc during testing, think of a Crashlytics module suddenly sending crash reports on test failures or a Logging module that prints garbage to your stdout. Usually you&#8217;d do something like this then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Module\n@TestInstallIn(\n    components = &#91;SingletonComponent::class],\n    replaces = &#91;LoggingModule::class]\n)\n<strong>internal object <\/strong>TestLoggingModule {\n    @Provides\n    @Singleton\n    <strong>fun<\/strong> provideLogger(): Logger = Logger { \/* no-op * \/ }\n}<\/code><\/pre>\n\n\n\n<p>All fine, but what if you now have a single test case where you want to check the log output? Well, you can&#8217;t uninstall a module installed via <code>@TestInstallIn<\/code>, but you can do a workaround: Install a module that <em>removes<\/em> the dependency, then add another regular module that adds your no-op implementation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@Module\n@TestInstallIn(\n    components = &#91;SingletonComponent::class],\n    replaces = &#91;LoggingModule::class]\n)\n<strong>internal object <\/strong>TestLoggingRemovalModule\n\n@Module\n@InstallIn(SingletonComponent::class)\n<strong>internal object <\/strong>TestLoggingModule {\n    @Provides\n    @Singleton\n    <strong>fun<\/strong> provideLogger(): Logger = Logger { \/* no-op * \/ }\n}<\/code><\/pre>\n\n\n\n<p>Now, in your test you can remove that module and have a custom implementation that you can verify against:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@HiltAndroidTest\n@RunWith(RobolectricTestRunner::class)\n@UninstallModules(TestLoggingModule::class)\n@Config(application = HiltTestApplication::class)\n<strong>internal class <\/strong>SomeLoggingTest {\n    @BindValue\n    <strong>val<\/strong> logger: Logger = MyFakeLogger()\n    ...\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Code Coverage<\/h2>\n\n\n\n<p>If your <code>@AndroidEntryPoint<\/code>s don&#8217;t show up in Jacoco&#8217;s code coverage reports as covered, even though you have tests for them, follow <a rel=\"noreferrer\" data-ss1638351369=\"1\" href=\"https:\/\/medium.com\/livefront\/dagger-hilt-testing-injected-android-components-with-code-coverage-30089a1f6872\">this excellent post<\/a> and choose whether you want to keep using the Hilt Gradle plugin or not.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrap-up<\/h2>\n\n\n\n<p>Dagger Hilt makes testing a lot easier; the ability to replace dependencies for each test separately is a real game changer. <\/p>\n\n\n\n<p>What&#8217;s also true is that it is still Dagger, i.e. the configuration is complex, the error messages cryptic at best and &#8211; this is new (at least for me) &#8211; Hilt compilation issues have occasionally to be fixed by cleaning your module, because there seem to be issues with incremental compilation. Not neccessarily confidence-inspiring, but at least you know how to fix things.<\/p>\n\n\n\n<p>I hope I could help you out with some my learnings, let me know what you think! <\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a loose list of learnings I had when I first came in contact with Dagger Hilt, especially in regards to testing. So, without further ado, let&#8217;s get into it. Documentation While the documentation on Dagger Hilt on developer.android.com is already quite exhaustive, I figured I missed a couple of important information and gotchas &hellip; <a href=\"https:\/\/www.thomaskeller.biz\/blog\/2021\/12\/01\/dagger-hilt-learnings\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Dagger Hilt Learnings<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1400","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/1400","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/comments?post=1400"}],"version-history":[{"count":15,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/1400\/revisions"}],"predecessor-version":[{"id":1430,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/1400\/revisions\/1430"}],"wp:attachment":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/media?parent=1400"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/categories?post=1400"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/tags?post=1400"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}