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.socket.DisableSockets;
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.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
import java.sql.Connection;
import java.sql.SQLException;

import static java.sql.DriverManager.getConnection;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringUsageTest.class)
@TestExecutionListeners(value = SniffySpringTestListener.class, mergeMode = MERGE_WITH_DEFAULTS) // (1)
public class SpringUsageTest {

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

    @Test
    @DisableSockets // (5)
    public void testDisableSockets() throws IOException {
        try {
            new Socket("google.com", 443); // (6)
            fail("Sniffy should have thrown ConnectException");
        } catch (ConnectException e) {
            assertNotNull(e);
        }
    }

}
  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.

  5. - Add @DisableSockets annotation on your test method or test class and any attempt to open a network connection will fail

  6. - All socket operations executed within test method annotated with @DisableSockets will throw a java.net.ConnectException

@SharedConnection

Sniffy provides convenient annotations for shared connection data source in Spring unit tests. Consider example below:

package io.sniffy.test.spring.usage;

import io.sniffy.test.spring.DataSourceTestConfiguration;
import io.sniffy.test.spring.EnableSharedConnection;
import io.sniffy.test.spring.SharedConnection;
import io.sniffy.test.spring.SniffySpringTestListener;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import ru.yandex.qatools.allure.annotations.Features;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;

import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceTestConfiguration.class, SpringSharedConnectionUsageTest.class})
@TestExecutionListeners(value = SniffySpringTestListener.class, mergeMode = MERGE_WITH_DEFAULTS) // (1)
@EnableSharedConnection // (2)
@Transactional
@Rollback
public class SpringSharedConnectionUsageTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    @SharedConnection // (3)
    @Features("issue/344")
    public void testSharedConnectionTwoRows() throws SQLException, ExecutionException, InterruptedException {

        jdbcTemplate.batchUpdate(
                "INSERT INTO PUBLIC.PROJECT (ID, NAME) VALUES (SEQ_PROJECT.NEXTVAL, ?)",
                Arrays.asList(new Object[]{"foo"}, new Object[]{"bar"})
        ); // (4)

        assertEquals(2, newSingleThreadExecutor().submit(
                () -> jdbcTemplate.queryForObject("SELECT COUNT(*) FROM PUBLIC.PROJECT", Integer.class) // (5)
        ).get().intValue());

    }

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

  2. - @EnableSharedConnection will automatically wrap all your data sources with SharedConnectionDataSource

  3. - @SharedConnection annotation will make the current connection (started because of @Transactional annotation before each test) as master

  4. - Insert two rows into table using master connection

  5. - Another slave connection obtained in another thread will still see the results of these inserts although the isolation level is READ_COMITTED and master transaction has not been committed

Troubleshooting

  1. Spring test context is no longer loaded when I add @TestExecutionListeners(SniffySpringTestListener.class)

The reason is that by default Spring does not merge the test execution listeners. Using this configuration you’re removing the predefined listeners which initialize the context. Change the mergeMode parameter to MERGE_WITH_DEFAULTS as shown below:

@TestExecutionListeners(value = SniffySpringTestListener.class, mergeMode = MERGE_WITH_DEFAULTS)