Re: Creating a partial mock from an abstract class with injected fields

676 views
Skip to first unread message

Stephan Classen

unread,
Apr 30, 2014, 6:41:09 AM4/30/14
to Anton Beloglazov, juk...@googlegroups.com

Ok first thing:
You should not test abstract classes but the concrete class which extends your base class. You can use google to find various reasons for this. The most obvious one is, that a subclass may change the behavior of a super class and therefore break the contract you have tested.

Never the less here is how you can do it:
Take your second approach and change the before method to

@Before
public void setupMocks(Injector injector) throws SQLException {
when(dataSource.getConnection()).thenReturn(connection);

holder = mock(AbstractConnectionHolder.class, CALLS_REAL_METHODS);

injector.injectMembers(holder);
}

This should inject the dependencies into your mock.

Am 30.04.2014 10:33 schrieb Anton Beloglazov <anton.be...@gmail.com>:
Hi All,

I'm trying to test an abstract class that has some injected fields. Basically, the class is the following:

public abstract class AbstractConnectionHolder {

@Inject
private Configuration configuration;

@Inject
private ConnectionContext context;

protected ConnectionContext getConnectionContext() {
return context;
}

protected Configuration getConfiguration() {
return configuration;
}

public Connection openConnection() {
return getConnectionContext().openConnection();
}

public void closeConnection() {
getConnectionContext().closeConnection();
}

public Connection getConnection() {
return getConnectionContext().getConnection();
}

}

To test it, I'm trying to create a new partial mock for every test. I've managed to run the following:

@RunWith(JukitoRunner.class)
public class AbstractConnectionHolderTest {

@Inject
DataSource dataSource;

@Inject
Connection connection;

@Inject
Configuration configuration;

@Inject
ConnectionContext context;

public static class Module extends JukitoModule {
@Override
protected void configureTest() {
bind(Configuration.class).toInstance(new Configuration(new PostGISTemplates()));
bind(AbstractConnectionHolder.class).toInstance(
mock(AbstractConnectionHolder.class, CALLS_REAL_METHODS));
}
}

@Before
public void setupMocks() throws SQLException {
when(dataSource.getConnection()).thenReturn(connection);
}

@Test
public void shouldReturnConfiguration(AbstractConnectionHolder holder) {
assertThat(holder.getConfiguration(), is(configuration));
}

}

This runs, but it only allows me to have a single AbstractConnectionHolder mock for all tests. The ideal case would be to make it somehow inject a new AbstractConnectionHolder partial mock into each test, which I couldn't figure out how to do. Is this possible?

I also tried to manually create mocks in the @Before method as follows:

@RunWith(JukitoRunner.class)
public class AbstractConnectionHolderTest {

@Inject
DataSource dataSource;

@Inject
Connection connection;

@Inject
Configuration configuration;

@Inject
ConnectionContext context;

AbstractConnectionHolder holder;

public static class Module extends JukitoModule {
@Override
protected void configureTest() {
bind(Configuration.class).toInstance(new Configuration(new PostGISTemplates()));
}
}

@Before
public void setupMocks() throws SQLException {
when(dataSource.getConnection()).thenReturn(connection);
holder = mock(AbstractConnectionHolder.class, CALLS_REAL_METHODS);
}

@Test
public void shouldReturnConfiguration() {
assertThat(holder.getConfiguration(), is(configuration));
}

}

However, this fails with the following exception:

java.lang.AssertionError: 
Expected: is <com.mysema.query.sql.Configuration@7a79dbc7>
     but: was null
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:865)
at org.junit.Assert.assertThat(Assert.java:832)
at test.AbstractConnectionHolderTest.shouldReturnConfiguration(AbstractConnectionHolderTest.java:66)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.jukito.InjectedStatement.evaluate(InjectedStatement.java:96)
at org.jukito.InjectedBeforeStatements.evaluate(InjectedBeforeStatements.java:51)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.jukito.JukitoRunner.run(JukitoRunner.java:187)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


As I understand, for some reason in this case it wasn't able to inject the fields of AbstractConnectionHolder. Is there a way to solve the original problem or this injection issue?

Thanks a lot!

Best regards,
Anton

--
You received this message because you are subscribed to the Google Groups "Jukito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jukito+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Anton Beloglazov

unread,
May 1, 2014, 1:49:53 AM5/1/14
to Stephan Classen, juk...@googlegroups.com
Hi Stephan,

You suggestion works perfectly, thanks a lot! :) I'm going to test the subclasses nevertheless, but since I have some logic in the abstract class, I wanted to test that as well. 

Cheers,
Anton

Stephan Classen

unread,
May 1, 2014, 2:39:24 AM5/1/14
to juk...@googlegroups.com

I had the same idea when I started with TDD and stumbled over an abstract class.
I didn't use mockito but wrote my own concrete subclass which implemented all abstract methods by doing nothing...

In the end I had a bunch of duplicated tests. Since I tested the behavior of the abstract class through my special subclass and the productive subclasses. Some of the productive sub classes altered the behavior of the base class slightly. So  I could not declare the method in the abstract class as final.

In the end I changed my abstract class to be an interface and moved all implementation which previously was in the abstract class to a new helper class. In the subclasses of my new interface I delegated the calls to the new helper class.
Using delegation I could test the common behavior in the helper class. And I could test which implementation of the interface relay on the default behavior and which defined there own logic.

Anton Beloglazov

unread,
May 2, 2014, 2:39:22 AM5/2/14
to juk...@googlegroups.com
Hi Stephan,

Thanks for your recommendations! When you moved the implementation from the abstract class to the helper class, did you avoid testing the methods that delegate calls to the helper class in the classes implementing the interface? Otherwise, it seems that testing the delegating methods also leads to duplicated tests, as the helper class was probably tested independently as well.

Thanks,
Anton
Reply all
Reply to author
Forward
0 new messages