Overview

Sniffy is a Java profiler which shows the results directly in your browser. It also brings profiling to your unit (or rather component) tests and allows you to disable certain outgoing connections for fault-tolerance testing.

In-browser profiler

demo

Asserting number of queries in unit tests

    @Rule public SniffyRule sniffyRule = new SniffyRule();

    @Rule public ExpectedException thrown = ExpectedException.none();

    @Test
    @SqlExpectation(count = @Count(1))
    public void testExpectedOneQueryGotOne() throws SQLException {
        DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa").createStatement().execute("SELECT 1 FROM DUAL"); (4)
    }

    @Test
    @SqlExpectation(count = @Count(max = 1), query = SqlStatement.SELECT)
    public void testExpectedNotMoreThanOneSelectGotTwo() throws SQLException {
        try (Statement statement = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa").createStatement()) {
            statement.execute("SELECT 1 FROM DUAL");
            statement.execute("SELECT 2 FROM DUAL");
        }
        thrown.expect(WrongNumberOfQueriesError.class);
    }

Testing bad connectivity

Discover all outgoing network connections from your server and disable them right from your browser:

network connections

Sniffy will throw a java.net.ConnectException when your application tries to connect to address disallowed by Sniffy.

Install

Standalone setup

Sniffy comes with an uber-jar which doesn’t require any additional dependencies to be installed. Just grab the sniffy-3.1.0.jar from our releases page and add it to the classpath of your application.

Warning
If you’re using an application server like Tomcat and you’re defining a datasource on application server level, sniffy-3.1.0.jar should be added to the common classloader classpath and should be absent in web application classpath.

Spring Boot Integration

If you’re using Spring Boot, add the dependency below to your project in order to use Sniffy.

Maven
<dependency>
    <groupId>io.sniffy</groupId>
    <artifactId>sniffy-web</artifactId>
    <version>3.1.0</version>
</dependency>
Gradle
dependencies {
    compile 'io.sniffy:sniffy-web:3.1.0'
}

Sniffy Test

Sniffy artifacts for unit test frameworks are distributed via Maven Central repository and can be downloaded using your favorite package manager.

JUnit

Maven
<dependency>
    <groupId>io.sniffy</groupId>
    <artifactId>sniffy-junit</artifactId>
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>
Gradle
dependencies {
    testCompile 'io.sniffy:sniffy-junit:3.1.0'
}

Spring Test

Maven
<dependency>
    <groupId>io.sniffy</groupId>
    <artifactId>sniffy-spring-test</artifactId>
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>
Gradle
dependencies {
    testCompile 'io.sniffy:sniffy-spring-test:3.1.0'
}

TestNG

Maven
<dependency>
    <groupId>io.sniffy</groupId>
    <artifactId>sniffy-testng</artifactId>
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>
Gradle
dependencies {
    testCompile 'io.sniffy:sniffy-testng:3.1.0'
}

Setup

Using Sniffy with Spring

If you are using Spring Boot, simply add @EnableSniffy to your application class:

package com.acme;

import io.sniffy.boot.EnableSniffy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableAutoConfiguration
@EnableSniffy (1)
public class Application {

    public static void main(String[] args) throws ClassNotFoundException {
        SpringApplication.run(Application.class, args);
    }

}
  1. Put this annotation on a class with spring configuration.

It will wrap all existing datasources with SniffyDataSource and also create an instance of SniffyFilter with bean id sniffyFilter which will inject sniffy widget into HTML pages. If you are using Spring Boot embedded servlet container t is sufficient - otherwise you should also create a mapping for this filter.

Datasource

Add sniffy to classpath

In order to intercept the SQL queries executed by your application you should use Sniffy datasource wrapper. At first you should add sniffy.jar to classpath of classloader which loads actual driver. If your datasource is created by application server and registered in JNDI for later usage, you should copy sniffy.jar so it would be available by application server common classloader. For example in case of Apache Tomcat you should place it to <TOMCAT-HOME>/lib folder

Enable sniffy for datasource

In order to enable sniffy on a datasource, just add sniffy: prefix and use io.sniffy.sql.SniffyDriver as a driver class name. For example jdbc:h2:~/test should be changed to sniffy:jdbc:h2:mem: The Sniffy JDBC driver class name to io.sniffy.sql.SniffyDriver

Filter

Enable Sniffy filter in web.xml

<filter>
    <filter-name>sniffer</filter-name>
    <filter-class>io.sniffy.servlet.SniffyFilter</filter-class>
    <init-param>
        <param-name>inject-html</param-name> (1)
        <param-value>true</param-value> <!-- default: false -->
    </init-param>
    <init-param>
        <param-name>enabled</param-name> (2)
        <param-value>true</param-value> <!-- default: true -->
    </init-param>
    <init-param>
        <param-name>exclude-pattern</param-name> (3)
        <param-value>^/vets.html$</param-value> <!-- optional -->
    </init-param>
</filter>
<filter-mapping>
    <filter-name>sniffer</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  1. Enables injection of Sniffy toolbar to HTML. If disabled the html remains untouched. You still can get the number of executed queries from Sniffy-Sql-Queries HTTP header.

  2. Allows disabling the Sniffy filter in web.xml

  3. Allows excluding some of the request URL’s from Sniffer filter

Configuration

Sniffy can be configured globally using Java system properties or environment variables. This configuration can be overriden in web.xml and/or @EnableSniffy annotation - see appropriate sections of documentation.

Warning
sniffy configuration is parsed only once and any changes made to system properties or environment variables in run-time won’t have any effect on Sniffy
Table 1. Table Configuration properties
System Property Environment Variable Description Default Value

-Dio.sniffy.monitorJdbc

IO_SNIFFY_MONITOR_JDBC

Monitor JDBC

true

-Dio.sniffy.monitorSocket

IO_SNIFFY_MONITOR_SOCKET

Monitor socket connections

true

-Dio.sniffy.filterEnabled

IO_SNIFFY_FILTER_ENABLED

Enable servlet filter

true

-Dio.sniffy.injectHtml

IO_SNIFFY_INJECT_HTML

Inject Sniffy HTML to result HTML

true

-Dio.sniffy.excludePattern

IO_SNIFFY_EXCLUDE_PATTERN

Regexp for excluding sniffy from certain servlet requests

Sniffy filter can also be enabled or disabled using HTTP query parameters and/or HTTP headers.

If Sniffy filter is currently disabled you can enable it by adding ?sniffy=true query parameter to your request - it will enable the sniffy for current request and will also set a sniffy cookie which will enable sniffy on subsequent requests.

For stateless clients who don’t maintain the cookie jar it might be more convenient to enable/disable Sniffy using Sniffy-Enabled: true / Sniffy-Enabled: false headers. Unlike the query parameter the HTTP header will only affect a single request.

Unit and component tests

Using Sniffy API

Sniffy provides a convenient API for validating the number of executed database queries, affected database rows or even number of active TCP connections. The main classes you should use are io.sniffy.Sniffy and io.sniffy.Spy.

Spy objects are responsible for recording the executed queries and bytes sent over the wire. Spy stores all the information since the moment it was created. Sniffy class provides convenient factory methods for creating Spy instances

Imperative approach

        Connection connection = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa"); (1)
        Spy<?> spy = Sniffy.spy(); (2)
        connection.createStatement().execute("SELECT 1 FROM DUAL"); (3)
        spy.verify(SqlQueries.atMostOneQuery()); (4)
        spy.verify(SqlQueries.noneQueries().otherThreads()); (5)
  1. Just add sniffy: in front of your JDBC connection URL in order to enable sniffer.

  2. Spy holds the amount of queries executed till the given amount of time. It acts as a base for further assertions.

  3. You do not need to modify your JDBC code.

  4. spy.verify(SqlQueries.atMostOneQuery()) throws an AssertionError if more than one query was executed.

  5. spy.verify(SqlQueries.noneQueries().otherThreads()) throws an AssertionError if at least one query was executed by the thread other than then current one.

Functional approach

        final Connection connection = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa"); (1)
        Sniffy.execute(
                () -> connection.createStatement().execute("SELECT 1 FROM DUAL")
        ).verify(SqlQueries.atMostOneQuery()); (2)
  1. Just add sniffy: in front of your JDBC connection URL in order to enable sniffer.

  2. Sniffy.execute() method executes the lambda expression and returns an instance of Spy which provides methods for validating the number of executed queries in given lambda/

Resource approach

        final Connection connection = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa"); (1)
        try (@SuppressWarnings("unused") Spy s = Sniffy. (2)
                expect(SqlQueries.atMostOneQuery()).
                expect(SqlQueries.noneQueries().otherThreads());
             Statement statement = connection.createStatement()) {
            statement.execute("SELECT 1 FROM DUAL");
        }
  1. Just add sniffy: in front of your JDBC connection URL in order to enable sniffer.

  2. You can use Sniffy in a try-with-resource block using expect methods instead of verify. When the try-with-resource block is completed, Sniffy will verify all the expectations defined

Integration with JUnit

Sniffy comes with a JUnit @Rule for quick integration with test framework. Just add @Rule public final SniffyRule sniffyRule = new SniffyRule(); to your JUnit test class and place appropriate expectations on your test methods like shown below.

package io.sniffy.test.junit.usage;

import io.sniffy.sql.SqlExpectation;
import io.sniffy.test.Count;
import io.sniffy.test.junit.SniffyRule;
import org.junit.Rule;
import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JUnitUsageTest {

    @Rule
    public final SniffyRule sniffyRule = new SniffyRule(); (1)

    @Test
    @SqlExpectation(count = @Count(1)) (2)
    public void testJUnitIntegration() throws SQLException {
        final Connection connection = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa"); (3)
        connection.createStatement().execute("SELECT 1 FROM DUAL"); (4)
    }

}
  1. - Integrate Sniffy to your test using @Rule annotation and a SniffyRule field.

  2. - Now just add @SqlExpectation annotation to define number of queries allowed for given method.

  3. - Just add sniffy: in front of your JDBC connection URL in order to enable sniffer.

  4. - Do not make any changes in your code - just add the @Rule SniffyRule and put annotations on your test method.

Integration with Spring Framework

Sniffy comes with a Spring Framework via SniffySpringTestListener spring @TestExecutionListener. Just add @TestExecutionListeners(SniffySpringTestListener.class) to your Spring test class and place appropriate expectations on your test methods like shown below.

package io.sniffy.test.spring.usage;

import io.sniffy.sql.SqlExpectation;
import io.sniffy.test.Count;
import io.sniffy.test.spring.SniffySpringTestListener;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringUsageTest.class)
@TestExecutionListeners(SniffySpringTestListener.class) (1)
public class SpringUsageTest {

    @Test
    @SqlExpectation(count = @Count(1)) (2)
    public void testJUnitIntegration() throws SQLException {
        final Connection connection = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa"); (3)
        connection.createStatement().execute("SELECT 1 FROM DUAL"); (4)
    }

}
  1. - Integrate Sniffy to your test using @TestExecutionListeners(SniffySpringTestListener.class).

  2. - Now just add @SqlExpectation annotation to define number of queries allowed for given method.

  3. - Just add sniffy: in front of your JDBC connection URL in order to enable sniffer.

  4. - Do not make any changes in your code - just add the @TestExecutionListeners(SniffySpringTestListener.class) and put annotations on your test method.

Integration with Test NG

Sniffy comes with a Test NG listener for quick integration with test framework. Just add @Listeners(SniffyTestNgListener.class) to your TestNG test class and place appropriate expectations on your test methods like shown below.

package io.sniffy.test.testng.usage;

import io.sniffy.sql.SqlExpectation;
import io.sniffy.test.Count;
import io.sniffy.test.testng.SniffyTestNgListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

@Listeners(SniffyTestNgListener.class) (1)
public class UsageTestNg {

    @Test
    @SqlExpectation(count = @Count(1)) (2)
    public void testJUnitIntegration() throws SQLException {
        final Connection connection = DriverManager.getConnection("sniffy:jdbc:h2:mem:", "sa", "sa"); (3)
        connection.createStatement().execute("SELECT 1 FROM DUAL"); (4)
    }

}
  1. - Integrate Sniffy to your test using @Listeners(SniffyTestNgListener.class).

  2. - Now just add @SqlExpectation annotation to define number of queries allowed for given method.

  3. - Just add sniffy: in front of your JDBC connection URL in order to enable sniffer.

  4. - Do not make any changes in your code - just add the @Listeners(SniffyTestNgListener.class) and put annotations on your test method.

Integration with Spock Framework

Spock Framework is a developer testing and specification framework for Java and Groovy applications.

Sniffy can be integrated with Spock Framework using Spy field and standard spock then block:

package io.sniffy.test.spock.usage

import groovy.sql.Sql
import io.sniffy.Sniffy
import io.sniffy.sql.SqlQueries
import io.sniffy.sql.WrongNumberOfQueriesError
import spock.lang.FailsWith
import spock.lang.Shared
import spock.lang.Specification

class SpockUsageSpec extends Specification {

    @Shared sql = Sql.newInstance("sniffy:jdbc:h2:mem:", "sa", "sa")

    def spy = Sniffy.spy()

    @FailsWith(WrongNumberOfQueriesError)
    "Execute single query - negative"() {
        when:
        sql.execute("SELECT 1 FROM DUAL")
        sql.execute("SELECT 1 FROM DUAL")

        then:
        spy.verify(SqlQueries.exactQueries(1))
    }

    def "Execute single query"() {
        when:
        sql.execute("SELECT 1 FROM DUAL")

        then:
        spy.verify(SqlQueries.exactQueries(1)).reset()

        when:
        sql.execute("SELECT 1 FROM DUAL")

        then:
        spy.verify(SqlQueries.exactQueries(1))
    }

    def "Execute single query - another one"() {
        when:
        sql.execute("SELECT 1 FROM DUAL")

        then:
        spy.verify(SqlQueries.exactQueries(1))
    }

}

Do not forget to call reset() method on the spy object if you have multiple when-then blocks in a single test method

Migration from previous versions

Migration from 3.0.x to 3.1.x

Maven artifacts

Sniffy test support has been extracted to a separate artifacts. You should now use following artifacts if you want to use Sniffy in your unit tests:

Table 2. Table Maven artifacts migration
Old artifact New artifact Test framework

io.sniffy:sniffy:test

io.sniffy:sniffy-junit:test

JUnit

io.sniffy:sniffy:test

io.sniffy:sniffy-spring-test:test

Spring Framework

io.sniffy:sniffy:test

io.sniffy:sniffy-testng:test

TestNG

io.sniffy:sniffy:test

io.sniffy:sniffy-core:test

Spock Framework

JDBC Connection String

sniffer: connection is deprecated as of Sniffy 3.1.0. You should use sniffy: instead like shown below:

sniffy:jdbc:h2:mem:

Deprecated Classes

Some of Sniffy classes are deprecated as of version 3.1.0 with an equivalent replacement as shown in the table below:

Table 3. Table Sniffy 3.1.0 deprecated classes
Deprecated class New class

io.sniffy.MockDriver

io.sniffy.sql.SniffyDriver

io.sniffy.Query

io.sniffy.sql.SqlStatement

io.sniffy.Sniffer

io.sniffy.Sniffy

io.sniffy.WrongNumberOfQueriesError

io.sniffy.sql.WrongNumberOfQueriesError

io.sniffy.servlet.SnifferFilter

io.sniffy.servlet.SniffyFilter

io.sniffy.junit.QueryCounter

io.sniffy.test.junit.SniffyRule

io.sniffy.spring.QueryCounterListener

io.sniffy.test.spring.SniffySpringTestListener

io.sniffy.Expectation

io.sniffy.sql.SqlExpectation

io.sniffy.Expectations

io.sniffy.sql.SqlExpectations

io.sniffy.NoQueriesAllowed

io.sniffy.sql.NoSql

io.sniffy.testng.QueryCounter

io.sniffy.test.testng.SniffyTestNgListener

Deprecated Methods

io.sniffy.Sniffer.*

Some methods in io.sniffy.Sniffer class are now deprecated and although they’re still available in io.sniffy.Sniffy class they will be removed completely in future versions of Sniffy.

Table 4. Table io.sniffy.Sniffer deprecated methods
Deprecated method Replacement

executedStatements()

spy().getExecutedStatements(Threads threadMatcher, boolean removeStackTraces)

expect*(…​)

expect(Spy.Expectation expectation)

io.sniffy.Spy.*

Some methods in io.sniffy.Spy class are now deprecated and will be removed completely in future versions of Sniffy.

Table 5. Table io.sniffy.Spy deprecated methods
Deprecated method Replacement

executedStatements()

spy().getExecutedStatements(Threads threadMatcher, boolean removeStackTraces)

expect*(…​)

expect(Spy.Expectation expectation)

verify*(…​)

verify(Spy.Expectation expectation)