jars.nosqlunit-documentation.0.10.0.source-code.advanced.xml Maven / Gradle / Ivy
<?xml version="1.0" encoding="UTF-8"?> <chapter version="5.0" xml:id="advanced" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:svg="http://www.w3.org/2000/svg" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:db="http://docbook.org/ns/docbook"> <title>Advanced Usage</title> <section> <title xml:id="advanced.redis_embedded.section">Embedded In-Memory Redis</title> <para> When you are writing unit tests you should keep in mind that they must run fast, which implies, among other things, no interaction with IO subsystem (disk, network, ...). To avoid this interaction in database unit tests, there are embedded in-memory databases like <emphasis>H2</emphasis> , <emphasis>HSQLDB</emphasis> , <emphasis>Derby</emphasis> or in case of <emphasis>NoSQL</emphasis> , engines like <emphasis>Neo4j</emphasis> or <emphasis>Cassandra</emphasis> have their own implementation. But <emphasis>Redis</emphasis> does not have any way to create an embedded in-memory instance in Java. For this reason I have written an embedded in-memory <emphasis>Redis</emphasis> implementation based on <emphasis>Jedis</emphasis> project. </para> <para> If you are using <emphasis role="bold">NoSQLUnit</emphasis> you only have to register embedded <emphasis>Redis</emphasis> rule as described <link linkend="program.redis_embedded_conf">here</link> , and internally <emphasis role="bold">NoSQLUnit</emphasis> will create instance for you, and you will be able to <link linkend="advanced.jsr330-title">inject</link> the instance into your code. </para> <para> But also can be used outside umbrella of <emphasis role="bold">NoSQLUnit</emphasis> , by instantiating manually, as described in next <link linkend="advanced.instantiate-embedded-redis">example:</link> </para> <example xml:id="advanced.instantiate-embedded-redis"> <title>Embedded In-Memory Redis.</title> <programlisting language="java"><![CDATA[EmbeddedRedisBuilder embeddedRedisBuilder = new EmbeddedRedisBuilder(); Jedis jedis = embeddedRedisBuilder.createEmbeddedJedis(); ]]></programlisting> </example> <para>Notice that Jedis class is the main class defined by Jedis project but proxied to use in-memory data instead of sending requests to remote server. </para> <para> Almost all <emphasis>Redis</emphasis> operations have been implemented but it has some limitations: </para> <para> <itemizedlist> <listitem> <para> Connection commands do nothing, they do not throw any exception but neither do any action. In fact would not have sense that they do something. </para> </listitem> <listitem> <para> Scripting commands are not supported, and an <classname>UnsupportedOperationException</classname> will be thrown if they are called. </para> </listitem> <listitem> <para> Transaction commands are not supported, but they do not throw any exception, simply returns a null value and in cases of List return type, an empty list is returned. </para> </listitem> <listitem> <para> Pub/Sub commands do nothing. </para> </listitem> <listitem> <para> Server commands are implemented, but there are some commands that have no sense and returns a constant result: </para> <para> <emphasis>move</emphasis> always return 1. </para> <para> <emphasis>debug</emphasis> commands throws an UnsupportedOperationException. </para> <para> <emphasis>bgrewriteaof, save, bgsave, configSet, configResetStat, salveOf, slaveOfNone and slowLogReset </emphasis> returns an OK. </para> <para> <emphasis>configGet, slowLogGet and slowLogGetBinary</emphasis> returns an empty list. </para> </listitem> <listitem> <para> From Key commands, only sort by pattern is not supported. </para> </listitem> </itemizedlist> </para> <para> All the other operations, including flushing, expiration control, and each operation of every datatype is supported in the same way Jedis support it. Note that expiration management is also implemented as described in Redis manual. </para> <warning> This implementation of Redis is provided for testing purposes not as a substitution of Redis. Feel free to notify any issue of this implementation so can be fixed or implemented. </warning> </section> <section> <title xml:id="advanced.multipleinstances-title">Managing lifecycle of multiple instances</title> <para> Sometimes your test will require that more than one instance of same database server (running in different ports) was started. For example for testing database sharding. In <link linkend="advanced.multipleinstances-database">next example</link> we see how to configure <emphasis role="bold">NoSQLUnit</emphasis> to manage lifecycle of multiple instances. </para> <example xml:id="advanced.multipleinstances-database"> <title>Multiple Instances of Redis.</title> <programlisting language="java"><![CDATA[@ClassRule public static ManagedRedis managedRedis79 = newManagedRedisRule().redisPath("/opt/redis-2.4.16") .targetPath("target/redis1") .configurationPath(getAbsoluteFilePath("src/test/resources/redis_6379.conf")) .port(6379) .build(); @ClassRule public static ManagedRedis managedRedis80 = newManagedRedisRule().redisPath("/opt/redis-2.4.16") .targetPath("target/redis2") .configurationPath(getAbsoluteFilePath("src/test/resources/redis_6380.conf")) .port(6380) .build(); ]]></programlisting> </example> <warning>Note that target path should be set to different values for each instance, if not some started processes could not be shutdown. </warning> </section> <section> <title xml:id="advanced.fast-way-title">Fast Way</title> <para> When you instantiate a Rule for maintaining database into known state ( <classname>MongoDbRule</classname> , <classname>Neo4jRule</classname> , ...) <emphasis role="bold">NoSQLUnit</emphasis> requires you set a configuration object with properties like host, port, database name, ... but although most of the time default values are enough, we still need to create the configuration object, which means our code becomes harder to read. </para> <para> We can avoid this by using an inner builder inside each rule, which creates for us a Rule with default parameters set. For example for <classname>Neo4jRule</classname> : </para> <example xml:id="advanced.fastway-database"> <title>Embedded Neo4jRule with defaults.</title> <programlisting language="java"><![CDATA[import static com.lordofthejars.nosqlunit.neo4j.Neo4jRule.Neo4jRuleBuilder.newNeo4jRule; @Rule public Neo4jRule neo4jRule = newNeo4jRule().defaultEmbeddedNeo4j();]]></programlisting> </example> <para> In previous <link linkend="advanced.fastway-database">example</link> <classname>Neo4jRule</classname> is configured to be used as embedded approach with default parameters. </para> <para> Another example using <classname>CassandraRule</classname> in managed way. </para> <example xml:id="advanced.fastway-managed-database"> <title>Managed Cassandra with defaults.</title> <programlisting language="java"><![CDATA[import static com.lordofthejars.nosqlunit.cassandra.CassandraRule.CassandraRuleBuilder.newCassandraRule; @Rule public CassandraRule cassandraRule = newCassandraRule().defaultManagedCassandra("Test Cluster");]]></programlisting> </example> <para>And each Rule contains their builder class to create default values. </para> </section> <section> <title xml:id="advanced.simultaneous-engine-title">Simultaneous engines</title> <para> Sometimes applications will contain more than one <emphasis>NoSQL</emphasis> engine, for example some parts of your model will be expressed better as a graph ( <application>Neo4J</application> for example), but other parts will be more natural in a column way (for example using <application>Cassandra</application> ). <emphasis role="bold">NoSQLUnit</emphasis> supports this kind of scenarios by providing in integration tests a way to not load all datasets into one system, but choosing which datasets are stored in each backend. </para> <para> For declaring more than one engine, you must give a name to each database <emphasis>Rule</emphasis> using <function>connectionIdentifier()</function> method in configuration instance. </para> <example xml:id="advanced.name-database"> <title>Given a name database rule</title> <programlisting language="java"><![CDATA[@Rule public MongoDbRule remoteMongoDbRule1 = new MongoDbRule(mongoDb() .databaseName("test").connectionIdentifier("one").build() ,this);]]></programlisting> </example> <para> And also you need to provide an identified dataset for each engine, by using <function>withSelectiveLocations</function> attribute of <function>@UsingDataSet</function> annotation. You must set up the pair "named connection" / datasets. </para> <example xml:id="advanced.dataset-selective"> <title>Selective dataset example</title> <programlisting language="java"><![CDATA[@UsingDataSet(withSelectiveLocations = { @Selective(identifier = "one", locations = "test3") }, loadStrategy = LoadStrategyEnum.REFRESH)]]></programlisting> </example> <para> In <link linkend="advanced.dataset-selective">example</link> we are refreshing database declared on <link linkend="advanced.name-database">previous example</link> with data located at <emphasis>test3</emphasis> file. </para> <para> Also works in expectations annotation: </para> <example xml:id="advanced.expected-dataset-selective"> <title>Selective expectation example</title> <programlisting language="java"><![CDATA[@ShouldMatchDataSet(withSelectiveMatcher = { @SelectiveMatcher(identifier = "one", location = "test3") })]]></programlisting> </example> <para> When you use more than one engine at a time you should take under consideration next rules: </para> <itemizedlist> <listitem> If location attribute is set, it will use it and will ignore <function>withSelectiveMatcher</function> attribute data. Location data is populated through all registered systems. </listitem> <listitem> If location is not set, then system tries to insert data defined in <function>withSelectiveMatcher</function> attribute to each backend. </listitem> <listitem> If <function>withSelectiveMatcher</function> attribute is not set, then default strategy (explained in <link linkend="seeding_database">section</link> ) is taken. Note that default strategy will replicate all datasets to defined engines. </listitem> </itemizedlist> <para> You can also use the same approach for inserting data into same engine but in different databases. If you have one <application>MongoDb</application> instance with two databases, you can also write tests for both databases at one time. For example: </para> <example xml:id="advanced.multiple-mongodb"> <title>Multiple connections example</title> <programlisting language="java"><![CDATA[@Rule public MongoDbRule remoteMongoDbRule1 = new MongoDbRule(mongoDb() .databaseName("test").connectionIdentifier("one").build() ,this); @Rule public MongoDbRule remoteMongoDbRule2 = new MongoDbRule(mongoDb() .databaseName("test2").connectionIdentifier("two").build() ,this); @Test @UsingDataSet(withSelectiveLocations = { @Selective(identifier = "one", locations = "json.test"), @Selective(identifier = "two", locations = "json3.test") }, loadStrategy = LoadStrategyEnum.CLEAN_INSERT) public void my_test() {...} ]]></programlisting> </example> </section> <section> <title xml:id="advanced.jsr330-title">Support for JSR-330</title> <para> <emphasis role="bold">NoSQLUnit</emphasis> supports two annotations of <acronym>JSR-330</acronym> aka Dependency Injection for Java. Concretely <classname>@Inject</classname> and <classname>@Named</classname> annotations. </para> <para> During test execution you may need to access underlying class used to load and assert data to execute extra operations to backend. <emphasis role="bold">NoSQLUnit</emphasis> will inspect <classname>@Inject</classname> annotations of test fields, and try to set own driver to attribute. For example in case of <application>MongoDb</application> , <classname>com.mongodb.Mongo</classname> instance will be injected. </para> <example xml:id="advanced.injection"> <title>Injection example</title> <programlisting language="java"><![CDATA[@Rule public MongoDbRule remoteMongoDbRule1 = new MongoDbRule(mongoDb() .databaseName("test").build() ,this); @Inject private Mongo mongo;]]></programlisting> </example> <warning> <para> Note that in <link linkend="advanced.injection">example</link> we are setting <varname>this</varname> as second parameter to the Rule. This is only required in versions of JUnit prior to 4.11. In new versions is no longer required passing the this parameter. </para> </warning> <para> But if you are using more than one engine at same time (see <link linkend="advanced.simultaneous-engine-title">chapter</link> ) you need a way to distinguish each connection. For fixing this problem, you must use <classname>@Named</classname> annotation by putting the identifier given in configuration instance. For example: </para> <example xml:id="advanced.named-injection"> <title>Named injection example</title> <programlisting language="java"><![CDATA[@Rule public MongoDbRule remoteMongoDbRule1 = new MongoDbRule(mongoDb() .databaseName("test").connectionIdentifier("one").build() ,this); @Rule public MongoDbRule remoteMongoDbRule2 = new MongoDbRule(mongoDb() .databaseName("test2").connectionIdentifier("two").build() ,this); @Named("one") @Inject private Mongo mongo1; @Named("two") @Inject private Mongo mongo2;]]></programlisting> </example> </section> </chapter>
© 2015 - 2025 Weber Informatics LLC | Privacy Policy