DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • State Transitions With Spring Integration
  • Java Applications Log Message Analytics Using Splunk
  • Throttling in Spring Integration
  • The 10 Laws of Testing

Trending

  • How To Verify Database Connection From a Spring Boot Application
  • Parallelism in ConcurrentHashMap
  • Memory Management in Java: An Introduction
  • Best Plugins For JetBrains IDEs
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. How to use Mock/Stub in Spring Integration Tests

How to use Mock/Stub in Spring Integration Tests

Hippoom Zhou user avatar by
Hippoom Zhou
·
Apr. 03, 13 · Interview
Like (1)
Save
Tweet
Share
50.74K Views

Join the DZone community and get the full member experience.

Join For Free

Generally, you pick up a subset of components in some integration tests to check if they are glued as expected.  To achieve this,  they are usually really invoked, but sometimes, it is too expensive to do so.  For example, Component  A invokes Component B, and Component B has a dependency on an external system which does not have a test server. We really want to verify the configurations, it seems the only way is replacing Component B  with test double after wiring Component A and B.

Let's start with Strategy A: Manual Injecting

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = "classpath:config.xml")  
public class SomeAppIntegrationTestsUsingManualReplacing {  
  
    private Mockery context = new JUnit4Mockery();     (1)  
  
    private SomeInterface mock = context.mock(SomeInterface.class);   (2)  
  
    @Resource(name = "someApp")  
    private SomeApp someApp;                  (3)  
  
    @Before  
    public void replaceDependenceWithMock() {  
        someApp.setDependence(mock);          (4)  
    }  
  
    @DirtiesContext 
    @Test  
    public void returnsHelloWorldIfDependenceIsAvailable() throws Exception {  
  
        context.checking(new Expectations() {  
            {  
                allowing(mock).isAvailable();          
                will(returnValue(true));         (5)  
            }  
        });  
  
        String actual = someApp.returnHelloWorld();  
        assertEquals("helloWorld", actual);  
        context.assertIsSatisfied(); (6)  
    }  
} 


We get a spring bean someApp(Component A in this case), and it has a denpendence on SomeInterface's(Component B in this case).  We inject mock (declare and init at step 4) to someApp, thus the test passes without sending request to the external system.  The context.assertIsSatisfied()(at step 6 ) is very important as we use SpringJUnit4ClassRunner as junit runner instead of JMock, so you have to explictly assert that all expectations are satisfied.

 There are two downsides of the previous strategy:

Firstly, if there are more than one mock, you have to inject  them one by one, which is very tedious especially when you need to inject mocks into serveral spring bean.

Secondly, the wiring is not tested.  For example, if I forget to write  <property name="beanName" ref="bean" /> the integration tests using manual inject strategy is not going to tell.

Strategy B: Using predefined BeanPostProcessor

Spring provides BeanPostProcessor which is very useful when you want to replace some bean after the wiring is done.  According to the reference, application context will auto detect all BeanPostProcessor registered in metadata(usually in xml format). 

public class PredefinedBeanPostProcessor implements BeanPostProcessor {  
  
    public Mockery context = new JUnit4Mockery();    (1)  
  
    public SomeInterface mock = context.mock(SomeInterface.class);   (2)  
  
    @Override  
    public Object postProcessBeforeInitialization(Object bean, String beanName)  
            throws BeansException {  
        return bean;  
    }  
  
    @Override  
    public Object postProcessAfterInitialization(Object bean, String beanName)  
            throws BeansException {  
        if ("dependence".equals(beanName)) {  
            return mock;  
        } else {  
            return bean;  
        }  
    }  
}  

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = { "classpath:config.xml",  
        "classpath:predefined.xml" })   (1)  
public class SomeAppIntegrationTestsUsingPredefinedReplacing {  
  
    @Resource(name = "someApp")  
    private SomeApp someApp;  
  
    @Resource(name = "predefined")  
    private PredefinedBeanPostProcessor fixture;  
  
    @Test  
    public void returnsHelloWorldIfDependenceIsAvailable() throws Exception {  
  
        fixture.context.checking(new Expectations() {  
            {  
                allowing(fixture.mock).isAvailable();  
                will(returnValue(true));  
            }  
        });  
  
        String actual = someApp.returnHelloWorld();  
        assertEquals("helloWorld", actual);  
        fixture.context.assertIsSatisfied();  
    }  
}  

Notice there is an extra config xml in which the PredefinedBeanPostProcessor is registered(at step 1).  The predefined.xml is placed in src/test/resources/, so it will not be packed into the artifact for production.

 For each test, using Strategy B requires inputting both a java file and a xml which is quite verbose.

 Now we have learned the pros and cons of  Strategy A and Strategy B.  What about a hybrid version -- killing two birds with one stone.  Therefore we have the next strategy.

Strategy C:Dynamic Injecting 

public class TestDoubleInjector implements BeanPostProcessor {  
  
    private static Map<String, Object> MOCKS = new HashMap<String, Object>(); (1)  
  
    @Override  
    public Object postProcessBeforeInitialization(Object bean, String beanName)  
            throws BeansException {  
        return bean;  
    }  
  
    @Override  
    public Object postProcessAfterInitialization(Object bean, String beanName)  
            throws BeansException {  
        if (MOCKS.containsKey(beanName)) {  
            return MOCKS.get(beanName);  
        }  
        return bean;  
    }  
  
    public void addMock(String beanName, Object mock) {  
        MOCKS.put(beanName, mock);  
    }  
  
    public void clear() {  
        MOCKS.clear();  
    }  
  
}  

@RunWith(JMock.class)  
public class SomeAppIntegrationTestsUsingDynamicReplacing {  
  
    private Mockery context = new JUnit4Mockery();  
  
    private SomeInterface mock = context.mock(SomeInterface.class);  
  
    private SomeApp someApp;  
  
    private ConfigurableApplicationContext applicationContext;  
  
    private TestDoubleInjector fixture = new TestDoubleInjector(); (1)  
  
    @Before  
    public void replaceDependenceWithMock() {  
  
        fixture.addMock("dependence", mock);  (2)  
  
        applicationContext = new ClassPathXmlApplicationContext(new String[] {  
                "classpath:config.xml", "classpath:dynamic.xml" });  (3)  
        someApp = (SomeApp) applicationContext.getBean("someApp");  
    }  
  
    @Test  
    public void returnsHelloWorldIfDependenceIsAvailable() throws Exception {  
  
        context.checking(new Expectations() {  
            {  
                allowing(mock).isAvailable();  
                will(returnValue(true));  
            }  
        });  
  
        String actual = someApp.returnHelloWorld();  
        assertEquals("helloWorld", actual);  
    }  
  
    @After  
    public void clean() {  
        applicationContext.close();  
        fixture.clear();  
    }  
}  

The TestDoubleInjector class is an implementation of Monostate pattern. Mocks are added to the static map before the application context being created. When another TestDoubleInjector instance (defined in dynamic.xml) is initiated, it can share the static map for replacement.  Just beware to clear the static map after tests. 

By the way, you could use Stub instead of Mocks with same strategies. 

Please do not hesitate to contact me if you might have any questions.  And I do appreciate it, if you could let me know you have a better idea. Thanks!  

Resources:

http://www.jmock.org

http://www.oracle.com/technetwork/articles/entarch/spring-aop-with-ejb5-093994.html(I saw BeanPostProcessor the first time in this post)

Testing Spring Framework Spring Integration Integration

Opinions expressed by DZone contributors are their own.

Related

  • State Transitions With Spring Integration
  • Java Applications Log Message Analytics Using Splunk
  • Throttling in Spring Integration
  • The 10 Laws of Testing

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: