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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
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

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Integrating Jenkins With Playwright TypeScript: A Complete Guide
  • Supercharging Pytest: Integration With External Tools
  • Mocking and Its Importance in Integration and E2E Testing
  • Seamless CI/CD Integration: Playwright and GitHub Actions

Trending

  • Automating Data Pipelines: Generating PySpark and SQL Jobs With LLMs in Cloudera
  • 5 Subtle Indicators Your Development Environment Is Under Siege
  • The Human Side of Logs: What Unstructured Data Is Trying to Tell You
  • Automatic Code Transformation With OpenRewrite
  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

By 
Hippoom Zhou user avatar
Hippoom Zhou
·
Apr. 03, 13 · Interview
Likes (1)
Comment
Save
Tweet
Share
51.5K 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

  • Integrating Jenkins With Playwright TypeScript: A Complete Guide
  • Supercharging Pytest: Integration With External Tools
  • Mocking and Its Importance in Integration and E2E Testing
  • Seamless CI/CD Integration: Playwright and GitHub Actions

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • 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:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!