All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jars.nosqlunit-documentation.0.10.0.source-code.advanced.xml Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
<?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