blob: e4d833db42d730ea5272211a8384b03dd83381ee [file] [log] [blame]
<!DOCTYPE html><html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<title>Apache Sling :: OSGi Mocks</title>
<link rel="icon" href="/favicon.ico"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css"/>
<link rel="stylesheet" href="/res/css/site.css"/>
<script src='https://www.apachecon.com/event-images/snippet.js'></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css"/>
<script src='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js'></script><script>
hljs.initHighlightingOnLoad();
</script>
<!-- Matomo Web Analytics -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
/* We explicitly disable cookie tracking to avoid privacy issues */
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://matomo.privacy.apache.org/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '6']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
<link href='/pagefind/pagefind-ui.css' rel='stylesheet'><script src='/pagefind/pagefind-ui.js' type='text/javascript'></script>
<script>
window.addEventListener('DOMContentLoaded', (event) => {
new PagefindUI({ element: "#searchbox" });
});
</script>
</head> <body>
<div class="section">
<div class="level is-marginless">
<div class="logo">
<a href="https://sling.apache.org">
<img border="0" alt="Apache Sling" src="/res/logos/sling.svg"/>
</a>
</div><div class="header">
<a href="https://www.apache.org">
<img border="0" alt="Apache" src="/res/logos/apache.png"/>
</a>
</div>
</div><section class="searchbox level is-marginless">
<div id="searchbox"></div>
</section><div class="columns is-gapless">
<div class="column is-narrow sidemenu">
<div class="container">
<nav class="menu">
<ul class="menu-list box is-shadowless is-marginless">
<li>
<p class="menu-label">
<strong>Documentation</strong>
</p><ul>
<li><a href="/documentation.html">Overview</a></li><li><a href="/documentation/getting-started.html">Getting Started</a></li><li><a href="/documentation/the-sling-engine.html">The Sling Engine</a></li><li><a href="/documentation/development.html">Development</a></li><li><a href="/documentation/bundles.html">Bundles</a></li><li><a href="/documentation/tutorials-how-tos.html">Tutorials &amp; How-Tos</a></li><li><a href="/components/">Maven Plugins</a></li><li><a href="/documentation/configuration.html">Configuration</a></li>
</ul>
</li><li>
<p class="menu-label">
<strong>API Docs</strong>
</p><ul>
<li><a href="/apidocs/sling12/index.html">Sling 12</a></li><li><a href="/apidocs/sling11/index.html">Sling 11</a></li><li><a href="/apidocs/sling10/index.html">Sling 10</a></li><li><a href="/apidocs/sling9/index.html">Sling 9</a></li><li><a href="/documentation/apidocs.html">All versions</a></li>
</ul>
</li><li>
<p class="menu-label">
<strong>Support</strong>
</p><ul>
<li><a href="https://s.apache.org/sling.wiki">Wiki</a></li><li><a href="https://s.apache.org/sling.faq">FAQ</a></li><li><a href="/sitemap.html">Sitemap</a></li>
</ul>
</li><li>
<p class="menu-label">
<strong>Project Info</strong>
</p><ul>
<li><a href="/downloads.cgi">Downloads</a></li><li><a href="https://www.apache.org/licenses/">License</a></li><li><a href="/news.html">News</a></li><li><a href="/releases.html">Releases</a></li><li><a href="https://issues.apache.org/jira/browse/SLING">Issue Tracker</a></li><li><a href="/links.html">Links</a></li><li><a href="/contributing.html">Contributing</a></li><li><a href="/project-information.html">Project Information</a></li><li><a href="/project-information/security.html">Security</a></li>
</ul>
</li><li>
<p class="menu-label">
<strong>Source</strong>
</p><ul>
<li><a href="/repolist.html">Repositories</a></li><li><a href="https://gitbox.apache.org/repos/asf?s=sling">Git at Apache</a></li>
</ul>
</li><li>
<p class="menu-label">
<strong>Apache Software<br>Foundation</strong>
</p><ul>
<li><a href="https://www.apache.org/foundation/thanks.html">Thanks!</a></li><li><a href="https://www.apache.org/foundation/sponsorship.html">Become a Sponsor</a></li><li><a href="https://www.apache.org/foundation/buy_stuff.html">Buy Stuff</a></li>
</ul>
</li><li>
<a class="acevent" data-format="square" data-event="random"></a>
</li><li>
<a href="https://apache.org/foundation/contributing.html" class="column">
<img border="0" alt="Support the Apache Software Foundation!" src="/res/images/SupportApache-small.png" width="125"/>
</a>
</li>
</ul>
</nav>
</div>
</div><div class="column main">
<div class="box is-shadowless is-marginless">
<div class="level">
<div class="pagenav">
<div class="breadcrumb">
<ul>
<li>
<a href="/">
Home
</a>
</li><li>
<a href="/documentation.html">
Documentation
</a>
</li><li>
<a href="/documentation/development.html">
Development
</a>
</li>
</ul>
</div>
</div><div class="tags">
<span class="tag">
<a href="/tags/development.html">
development
</a>
</span><span class="tag">
<a href="/tags/testing.html">
testing
</a>
</span><span class="tag">
<a href="/tags/mocks.html">
mocks
</a>
</span><span class="tag">
<a href="/tags/osgi.html">
osgi
</a>
</span>
</div>
</div><h1 class="title">
OSGi Mocks
</h1><nav class="menu">
<ul class="menu-list box is-shadowless is-paddingless">
<li id="generatedToC">
<p class="menu-label">
<strong>Table of Contents</strong>
</p>
</li>
</ul>
</nav><script src='/res/jquery-3.2.1.min.js' type='text/javascript'></script><script src='/res/tocjs-1-1-2.js' type='text/javascript'></script><script type='text/javascript'>$(document).ready(function() { $('#generatedToC').toc({'selector':'h1[class!=title],h2,h3','ulClass':'menu-list'}); } );</script><div class="content is-marginless">
<div class="row" data-pagefind-body="true"><div><section><p>Mock implementation of selected OSGi APIs for easier testing.</p>
<p><!-- TODO reactivate TOC once JBake moves to flexmark-java -->
</p>
<h2><a href="#maven-dependency" id="maven-dependency">Maven Dependency</a></h2>
<p>For JUnit 5:</p>
<pre><code><!-- TODO syntax marker (#!xml) disabled -->&lt;dependency&gt;
&lt;groupId&gt;org.apache.sling&lt;/groupId&gt;
&lt;artifactId&gt;org.apache.sling.testing.osgi-mock.junit5&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>For JUnit 4:</p>
<pre><code><!-- TODO syntax marker (#!xml) disabled -->&lt;dependency&gt;
&lt;groupId&gt;org.apache.sling&lt;/groupId&gt;
&lt;artifactId&gt;org.apache.sling.testing.osgi-mock.junit4&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<p>See latest version on the <a href="/downloads.cgi">downloads page</a>.</p>
<p>There are two major version ranges available:</p>
<ul>
<li>osgi-mock 1.x: compatible with OSGi R4 and above, JUnit 4</li>
<li>osgi-mock 2.x: compatible with OSGi R6 and above, JUnit 4 and JUnit 5</li>
</ul>
<h2><a href="#implemented-mock-features" id="implemented-mock-features">Implemented mock features</a></h2>
<p>The mock implementation supports:</p>
<ul>
<li>Instantiating OSGi <code>Bundle</code>, <code>BundleContext</code> and <code>ComponentContext</code> objects and navigate between them.</li>
<li>Register OSGi SCR services and get references to service instances</li>
<li>Supports reading OSGi SCR metadata from <code>/OSGI-INF/&lt;pid&gt;.xml</code> and from <code>/OSGI-INF/serviceComponents.xml</code></li>
<li>Apply service properties/component configuration provided in unit test and from SCR metadata</li>
<li>Inject SCR dependencies - static and dynamic</li>
<li>Call lifecycle methods for activating, deactivating or modifying SCR components</li>
<li>Service and bundle listener implementation</li>
<li>Mock implementation of <code>LogService</code> which logs to SLF4J in JUnit context</li>
<li>Mock implementation of <code>EventAdmin</code> which supports <code>EventHandler</code> services</li>
<li>Mock implementation of <code>ConfigAdmin</code></li>
<li>Context Plugins</li>
</ul>
<p>Since osgi-mock 2.0.0:</p>
<ul>
<li>Support OSGi R6 and Declarative Services 1.3: Field-based reference bindings and component property types</li>
</ul>
<p>Since osgi-mock 3.4.0:</p>
<ul>
<li>Support direct construction of component property type <a href="#config-annotations">Config Annotations</a>.</li>
</ul>
<h2><a href="#usage" id="usage">Usage</a></h2>
<p>The <code>OsgiContext</code> object provides access to mock implementations of:</p>
<ul>
<li>OSGi Component Context</li>
<li>OSGi Bundle Context</li>
</ul>
<p>Additionally it supports:</p>
<ul>
<li>Registering and activating OSGi services and inject dependencies</li>
</ul>
<h3><a href="#junit-5-osgi-context-junit-extension" id="junit-5-osgi-context-junit-extension">JUnit 5: OSGi Context JUnit Extension</a></h3>
<p>The OSGi mock context can be injected into a JUnit test using a custom JUnit extension named <code>OsgiContextExtension</code>. This extension takes care of all initialization and cleanup tasks required to make sure all unit tests can run independently (and in parallel, if required).</p>
<p>Example:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->@ExtendWith(OsgiContextExtension.class)
public class ExampleTest {
private final OsgiContext context = new OsgiContext();
@Test
public void testSomething() {
// register and activate service with configuration
MyService service1 = context.registerInjectActivateService(new MyService(),
&quot;prop1&quot;, &quot;value1&quot;);
// get service instance
OtherService service2 = context.getService(OtherService.class);
}
}
</code></pre>
<p>It is possible to combine such a unit test with a <code>@ExtendWith</code> annotation e.g. for <a href="https://www.javadoc.io/page/org.mockito/mockito-junit-jupiter/latest/org/mockito/junit/jupiter/MockitoExtension.html">Mockito JUnit Jupiter Extension</a>.</p>
<p>The OsgiContext has to be defined as non-static field in combination with <code>@BeforeEach</code> and <code>@AfterEach</code> methods if you want to execute setup or tear down code for each test run. It is not supported to use a static field with <code>@BeforeAll</code> and <code>@AfterAll</code> methods. You should never try to instantiate a OsgiContext object within a <code>@BeforeEach</code> method, this may lead to duplicate context instances.</p>
<h3><a href="#junit-4-osgi-context-junit-rule" id="junit-4-osgi-context-junit-rule">JUnit 4: OSGi Context JUnit Rule</a></h3>
<p>The OSGi mock context can be injected into a JUnit test using a custom JUnit rule named <code>OsgiContext</code>. This rule takes care of all initialization and cleanup tasks required to make sure all unit tests can run independently (and in parallel, if required).</p>
<p>Example:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->public class ExampleTest {
@Rule
public final OsgiContext context = new OsgiContext();
@Test
public void testSomething() {
// register and activate service with configuration
MyService service1 = context.registerInjectActivateService(new MyService(),
&quot;prop1&quot;, &quot;value1&quot;);
// get service instance
OtherService service2 = context.getService(OtherService.class);
}
}
</code></pre>
<p>It is possible to combine such a unit test with a <code>@RunWith</code> annotation e.g. for <a href="https://www.javadoc.io/page/org.mockito/mockito-core/latest/org/mockito/junit/MockitoJUnitRunner.html">Mockito JUnit Runner</a>.</p>
<h3><a href="#getting-osgi-mock-objects" id="getting-osgi-mock-objects">Getting OSGi mock objects</a></h3>
<p>The factory class <code>MockOsgi</code> allows to instantiate the different mock implementations.</p>
<p>Example:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->// get bundle context
BundleContext bundleContext = MockOsgi.newBundleContext();
// get component context with configuration
BundleContext bundleContext = MockOsgi.newComponentContext(properties,
&quot;prop1&quot;, &quot;value1&quot;);
</code></pre>
<p>It is possible to simulate registering of OSGi services (backed by a simple hash map internally):</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->// register service
bundleContext.registerService(MyClass.class, myService, properties);
// get service instance
ServiceReference ref = bundleContext.getServiceReference(MyClass.class.getName());
MyClass service = bundleContext.getService(ref);
</code></pre>
<h3><a href="#activation-and-dependency-injection" id="activation-and-dependency-injection">Activation and Dependency Injection</a></h3>
<p>It is possible to simulate OSGi service activation, deactivation and dependency injection and the mock implementation tries to to its best to execute all as expected for an OSGi environment.</p>
<p>Example:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->// get bundle context
BundleContext bundleContext = MockOsgi.newBundleContext();
// create service instance manually
MyService service = new MyService();
// inject dependencies
MockOsgi.injectServices(service, bundleContext);
// activate service
MockOsgi.activate(service, props);
// operate with service...
// deactivate service
MockOsgi.deactivate(service);
</code></pre>
<p>Please note:</p>
<ul>
<li>You should ensure that you register you services in the correct order of their dependency chain. Only dynamic references will be handled automatically independent of registration order.</li>
<li>The injectServices, activate and deactivate Methods can only work properly when the SCR XML metadata files are preset in the classpath at <code>/OSGI-INF</code>. They are generated automatically by the Maven SCR plugin, but might be missing if your clean and build the project within your IDE (e.g. Eclipse). In this case you have to compile the project again with maven and can run the tests - or use a Maven IDE Integration like m2eclipse.</li>
</ul>
<h3><a href="#provide-your-own-configuration-via-configadmin" id="provide-your-own-configuration-via-configadmin">Provide your own configuration via ConfigAdmin</a></h3>
<p>If you want to provide your own configuration to an OSGi service that you do not register and activate itself in the mock context you can provide your own custom OSGi configuration via the mock implementation of the <code>ConfigAdmin</code> service.</p>
<p>Example:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->
ConfigurationAdmin configAdmin = context.getService(ConfigurationAdmin.class);
Configuration myServiceConfig = configAdmin.getConfiguration(MY_SERVICE_PID);
Dictionary&lt;String, Object&gt; props = new Hashtable&lt;String, Object&gt;();
props.put(&quot;prop1&quot;, &quot;value1&quot;);
myServiceConfig.update(props);
</code></pre>
<h3><a href="#context-plugins" id="context-plugins">Context Plugins</a></h3>
<p>OSGi Mocks supports &quot;Context Plugins&quot; that hook into the lifecycle of each test run and can prepare test setup before or after the other setUp actions, and execute test tear down code before or after the other tearDown action.</p>
<p>To define a plugin implement the <code>org.apache.sling.testing.mock.osgi.context.ContextPlugin&lt;OsgiContextImpl&gt;</code> interface. For convenience it is recommended to extend the abstract class <code>org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin&lt;OsgiContextImpl&gt;</code>. These plugins can be used with OSGi Mock context, but also with context instances deriving from it like Sling Mocks and AEM Mocks. In most cases you would just override the <code>afterSetUp</code> method. In this method you can register additional OSGi services or do other preparation work. It is recommended to define a constant pointing to a singleton of a plugin instance for using it.</p>
<p>To use a plugin in your unit test class, use the <code>OsgiContextBuilder</code> class instead of directly instantiating the <code>OsgiContext</code>class. This allows you in a fluent style to configure more options, with the <code>plugin(...)</code> method you can add one or more plugins.</p>
<p>Example:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->OsgiContext context = new OsgiContextBuilder().plugin(MY_PLUGIN).build();
</code></pre>
<p>More examples:</p>
<ul>
<li><a href="https://github.com/apache/sling-org-apache-sling-testing-caconfig-mock-plugin/blob/master/src/main/java/org/apache/sling/testing/mock/caconfig/ContextPlugins.java">Apache Sling Context-Aware Configuration Mock Plugin</a></li>
<li><a href="https://github.com/apache/sling-org-apache-sling-testing-caconfig-mock-plugin/blob/master/src/test/java/org/apache/sling/testing/mock/caconfig/ContextPluginsTest.java">Apache Sling Context-Aware Configuration Mock Plugin Test</a></li>
</ul>
<h2><a href="#config-annotations" id="config-annotations">Config Annotations</a></h2>
<p>Since osgi-mock 3.4.0, it is possible to use your component <code>Config</code> annotation test methods and classes, or use the provided <code>@SetConfig</code> and <code>@ConfigType</code> annotations to construct them for use as first-class values in unit tests.</p>
<h3><a href="#setconfig" id="setconfig"><code>@SetConfig</code></a></h3>
<p><code>@SetConfig</code> is used to declare a ConfigurationAdmin configuration update prior to execution of a test using a <code>@Component</code>-style property declaration.</p>
<p>Either the <code>pid</code> or <code>component</code> Class attribute must be specified for it to have any effect. If both are specified, the <code>pid</code> attribute takes precedence.</p>
<p>Multiple <code>@SetConfig</code> annotations may be specified on the test class and the test method. They will be applied in the order they are declared, <strong>starting with the class annotations, then the method annotations</strong>.</p>
<h3><a href="#configtype" id="configtype"><code>@ConfigType</code></a></h3>
<p><code>@ConfigType</code> is used to map a service component's <code>Config</code> annotation type to an optional <code>@Component</code>-style property declaration, or to a pid to get a configuration from <code>ConfigurationAdmin</code> when the type is injected as a test parameter or collected by a <code>ConfigCollector</code>.</p>
<h3><a href="#autoconfig" id="autoconfig"><code>@AutoConfig</code></a></h3>
<p><code>@AutoConfig(MyService.class)</code> is used to automatically convert a component property type annotation to a property map and install it using ConfigurationAdmin for the designated component class, so that a matching <code>context.registerInjectActivateService(MyService.class)</code> call will reflect the values of config annotation, without having to explicitly pass them as a <code>Map&lt;String, Object&gt;</code> in the method arguments.</p>
<p>An <code>@AutoConfig</code> annotation may be specified on the test class or the test method. If both are specified, the method annotation takes precedence.</p>
<p>All <code>@SetConfig</code> annotations in scope will be applied before <code>@AutoConfig</code>, if present, and <code>@ConfigType</code> annotations will be constructed after that.</p>
<p>Multiple <code>@ConfigType</code> annotations may be specified on the test class and the test method. They will be injected into matching parameters in the order they are declared, <strong>starting with the method annotations, then the class annotations</strong>.</p>
<p>Both osgi-mock.junit4 and osgi-mock.junit5 provide different approaches for convenient reflection and injection of these annotations.</p>
<h3><a href="#junit-5-osgiconfigparametersextension-junit-extension" id="junit-5-osgiconfigparametersextension-junit-extension">JUnit 5: <code>OsgiConfigParametersExtension</code> JUnit Extension</a></h3>
<p>Given an OSGi component class that looks like this:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Component(service = MyService.class)
public class MyService {
// specify runtime retention to allow for direct usage in unit tests
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
String path() default &quot;/&quot;;
}
private final String path;
@Activate
public MyService(Config config) {
this.path = config.path();
}
public String getPath() {
return path;
}
}
</code></pre>
<p>A companion unit test in JUnit 5 might look like this:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->import org.apache.sling.testing.mock.osgi.config.annotations.ConfigType;
import org.apache.sling.testing.mock.osgi.config.annotations.SetConfig;
import org.apache.sling.testing.mock.osgi.junit5.OsgiConfigParametersExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(OsgiConfigParametersExtension.class)
class MyServiceTest {
@Test
@MyService.Config(path = &quot;/apps&quot;) // requires @Retention(RetentionPolicy.RUNTIME)
void getPath(MyService.Config config) {
MyService myService = new MyService(config);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
@Test
@ConfigType(type = MyService.Config.class, property = &quot;path=/libs&quot;)
void getPath_ConfigType(MyService.Config config) {
MyService myService = new MyService(config);
assertEquals(&quot;/libs&quot;, myService.getPath());
}
@Test
@SetConfig(pid = &quot;new-pid&quot;, property = &quot;path=/content&quot;)
@ConfigType(pid = &quot;new-pid&quot;, type = MyService.Config.class)
void getPath_SetConfig(MyService.Config config) {
MyService myService = new MyService(config);
assertEquals(&quot;/content&quot;, myService.getPath());
}
}
</code></pre>
<p>There are multiple ways to declare a <code>Config</code> annotation and then use it as a test parameter.</p>
<p>Directly use the annotation on the test method and declare it as a test parameter:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->@Test
@MyService.Config(path = &quot;/apps&quot;)
void getPath(MyService.Config config) {
MyService myService = new MyService(config);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
</code></pre>
<p>Directly use the annotation on the test method, but use the <code>@AutoConfig(MyService.class)</code> annotation to install your component configuration behind the scenes, so that <code>registerInjectActivateService</code> will load it from ConfigurationAdmin:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->@Test
@AutoConfig(MyService.class)
@MyService.Config(path = &quot;/apps&quot;)
void getPath() {
MyService myService = context.registerInjectActivateService(MyService.class);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
</code></pre>
<p>To create more than one configurable service in your test, use the <code>@ConfigMap</code> annotation on a <code>Map&lt;String, Object&gt;</code> parameters to have the typed config annotations converted for use as Map arguments to <code>registerInjectActivateService</code>:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->
@Test
@MyServiceDependency.Config(allowedPaths = &quot;/apps&quot;)
@MyService.Config(path = &quot;/apps&quot;)
void getPath(@ConfigMap(MyServiceDependency.Config.class)
Map&lt;String, Object&gt; myDependencyConfig,
@ConfigMap(MyService.Config.class)
Map&lt;String, Object&gt; myServiceConfig) {
MyServiceDependency myDependency =
context.registerInjectActivateService(MyServiceDependency.class, myDependencyConfig);
MyService myService =
context.registerInjectActivateService(MyService.class, myServiceConfig);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
</code></pre>
<h3><a href="#junit-4-configcollector-junit-rule" id="junit-4-configcollector-junit-rule">JUnit 4: <code>ConfigCollector</code> JUnit Rule</a></h3>
<p>Given the same example OSGi component from before:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Component(service = MyService.class)
public class MyService {
// specify runtime retention to allow for direct usage in unit tests
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
String path() default &quot;/&quot;;
}
private final String path;
@Activate
public MyService(Config config) {
this.path = config.path();
}
public String getPath() {
return path;
}
}
</code></pre>
<p>A companion unit test in JUnit 4 might look like this:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->import org.apache.sling.testing.mock.osgi.config.annotations.ConfigType;
import org.apache.sling.testing.mock.osgi.config.annotations.SetConfig;
import org.apache.sling.testing.mock.osgi.junit.ConfigCollector;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.apache.sling.testing.mock.osgi.junit.OsgiContextBuilder;
import org.junit.Rule;
import static org.junit.Assert.assertEquals;
public class MyServiceTest {
@Rule
public OsgiContext context = new OsgiContextBuilder().build();
@Rule
public ConfigCollector configs = new ConfigCollector(context);
@Test
@MyService.Config(path = &quot;/apps&quot;) // requires @Retention(RetentionPolicy.RUNTIME)
public void myServiceMethod() {
MyService.Config config = configs.firstConfig(MyService.Config.class);
MyService myService = new MyService(config);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
@Test
@ConfigType(type = MyService.Config.class, property = &quot;path=/libs&quot;)
public void myServiceMethod() {
MyService.Config config = configs.firstConfig(MyService.Config.class);
MyService myService = new MyService(config);
assertEquals(&quot;/libs&quot;, myService.getPath());
}
@Test
@SetConfig(pid = &quot;new-pid&quot;, property = &quot;path=/content&quot;)
@ConfigType(pid = &quot;new-pid&quot;, type = MyService.Config.class)
public void myServiceMethod() {
MyService.Config config = configs.firstConfig(MyService.Config.class);
MyService myService = new MyService(config);
assertEquals(&quot;/content&quot;, myService.getPath());
}
}
</code></pre>
<p>In JUnit4 are multiple ways to declare a <code>Config</code> annotation and then use it as a test parameter.</p>
<p>Directly use the annotation on the test method and retrieve it from the <code>ConfigCollector</code> using the <code>firstConfig(Config.class)</code> method to pass to your component's <code>@Activate</code> constructor:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->@Rule
public ConfigCollector configs = new ConfigCollector(context);
@Test
@MyService.Config(path = &quot;/apps&quot;)
public void testGetPath() {
MyService.Config config = configs.firstConfig(MyService.Config.class);
MyService myService = new MyService(config);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
</code></pre>
<p>Directly use the annotation on the test method, but use the <code>@AutoConfig(MyService.class)</code> annotation to install your component configuration behind the scenes, so that <code>registerInjectActivateService</code> will load it from ConfigurationAdmin:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->@Rule
public ConfigCollector configs = new ConfigCollector(context);
@Test
@AutoConfig(MyService.class)
@MyService.Config(path = &quot;/apps&quot;)
public void testGetPath() {
MyService myService = context.registerInjectActivateService(MyService.class);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
</code></pre>
<p>To create more than one configurable service in your test, use the <code>ConfigCollector.firstConfigMap(Config.class)</code> method to return a <code>Map&lt;String, Object&gt;</code> converted from each <code>@Config</code> annotation for use as Map arguments to <code>registerInjectActivateService</code>:</p>
<pre><code><!-- TODO syntax marker (#!java) disabled -->@Rule
public ConfigCollector configs = new ConfigCollector(context);
@Test
@MyServiceDependency.Config(allowedPaths = &quot;/apps&quot;)
@MyService.Config(path = &quot;/apps&quot;)
public void testGetPath() {
Map&lt;String, Object&gt; myDependencyConfig = configs.firstConfigMap(MyServiceDependency.Config.class);
Map&lt;String, Object&gt; myServiceConfig = configs.firstConfigMap(MyService.Config.class);
MyServiceDependency myDependency = context.registerInjectActivateService(MyServiceDependency.class, myDependencyConfig);
MyService myService = context.registerInjectActivateService(MyService.class, myServiceConfig);
assertEquals(&quot;/apps&quot;, myService.getPath());
}
</code></pre>
<h3><a href="#config-annotations-slingcontext-compatibility" id="config-annotations-slingcontext-compatibility">Config Annotations: SlingContext Compatibility</a></h3>
<p>The OSGi Mock Config Annotations and JUnit4/JUnit5 extensions are compatible with the <code>SlingContext</code> from Sling Mocks and other libraries that provide extensions of <code>OsgiContextImpl</code>. The JUnit4 Rule or JUnit5 Extension will be available in test code as long as the osgi context provider's junit4 or junit5 library is explicitly or transitively dependent on the respective osgi-mock.junit4 or osgi-mock.junit5 dependency.</p>
</section></div></div><div data-pagefind-body="true" data-pagefind-weight="7.0" style="display:none;"> - ( OSGi Mocks )</div>
</div>
</div>
</div>
</div><footer class="footer">
<div class="content has-text-centered is-small">
<div class="editpagelink">
This page can be edited on GitHub at <a href="https://github.com/apache/sling-site/edit/master/src/main/jbake/content/documentation/development/osgi-mock.md">
content/documentation/development/osgi-mock.md
</a>
</div> <div class="revisionInfo">
Last modified by <span class="author">Stefan Seifert</span> on <span class="comment">2024-04-22</span>
</div><p>
Apache Sling, Sling, Apache, the Apache feather logo, and the Apache Sling project
logo are trademarks of The Apache Software Foundation. All other marks mentioned
may be trademarks or registered trademarks of their respective owners.
</p><p>
Copyright © 2007-2024<a href="https://www.apache.org/">
The Apache Software Foundation
</a>|<a href="https://privacy.apache.org/policies/privacy-policy-public.html">
Privacy Policy
</a>
</p>
</div>
</footer>
</div>
</body>
</html>