Reputation: 3426
I have a logging aspect defined around a particular service. I am using Spring AOP and advising a number of service methods in order to log specific application events related to those method calls.
For example, I am using the @AfterThrowing annotation to detect a failures in method calls so I can log accordingly. My service methods are marked @Transactional.
As you can probably tell, it is extremely important that the logging aspect logic is invoked after the transaction is committed, otherwise my logging aspect would miss any errors associated with transactions failing on commit. Worse still, any log messages associated with successful method invocation would be written even though the method actually failed once the transaction was committed.
I have this working perfectly by ensuring I define the aspect order precedence correctly. My biggest issue is that I want to be able to write a test (integration test seems the only option) that will categorically confirm that the order precedence is being honoured. Given that the order precedence is a configuration, it is easy to foresee someone coming along in the future and tweaking configuration without realising they are breaking critical code.
So, my theory is that I need to write a test that deliberately causes a transaction to fail on commit and then checks that the logic in my logging @AfterThrowing pointcuts are called afterwards.
Anyone ever come across this need before? I'm sure it is a common scenario when using AOP.
Upvotes: 2
Views: 688
Reputation: 6229
You probably want to load your entire Spring context for the tests except for the DataSource. For the database stuff, take a look at MockRunner. It has a good set of mock JDBC classes.
In your Spring context (maybe via a separate xml file), swap out your real DataSource with a MockDataSource
for the test. Retrieve this DataSource from the context in your test cases and you can do something like the following:
MockResultSet rs = new MockResultSet("SELECT 1");
rs.addRow(new Object[] { 1 });
MockConnection con = new MockConnection();
con.getPreparedStatementResultSetHandler().prepareResultSet("SELECT 1", rs);
PreparedStatement ps = connection.prepareStatement("SELECT 1");
ps.executeQuery()
Note that if you want to throw an exception, the result set handler (via AbstractResultSetHandler
) has a prepareThrowsSQLException()
which you can use to specify that a specific SQL string should throw an exception.
Mix and match the above with @Before
and @BeforeClass
methods depending on what you need to setup before each test case. Be aware that Mock JDBC Statements keep a record of all executed SQL, so if you are running a huge number of JDBC calls (millions?), memory / performance could be an issue. It may be easiest to just create a new Connection / Statement for each test case.
Finally, if you are using Maven, MockRunner pulls in a lot of other things you don't need for JDBC mocks. Here's how I have it defined in the pom.xml:
<dependency>
<groupId>com.mockrunner</groupId>
<artifactId>mockrunner-jdk1.5-j2ee1.3</artifactId>
<version>0.4</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
<groupId>cglib-nodep</groupId>
<artifactId>cglib-nodep</artifactId>
</exclusion>
<exclusion>
<groupId>jboss</groupId>
<artifactId>jboss-jee</artifactId>
</exclusion>
<exclusion>
<groupId>struts</groupId>
<artifactId>struts</artifactId>
</exclusion>
<exclusion>
<groupId>org.mockejb</groupId>
<artifactId>mockejb</artifactId>
</exclusion>
<exclusion>
<groupId>nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</exclusion>
<exclusion>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
</exclusion>
<exclusion>
<groupId>commons-digester</groupId>
<artifactId>commons-digester</artifactId>
</exclusion>
<exclusion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
Upvotes: 1
Reputation: 40061
If you write a test that is context aware you could get all aspects from the context and assert that the order is the intended.
Another solution is to find any resource that could throw a duplicate key exception and just call it twice with the same value. At least the second call should log the exception.
Upvotes: 1