How to share #MockBeans and mock methods among my tests without inheritance? - spring

I have a base test scenario that will be used by other integration tests. This scenario includes some mock beans (#MockBean) for external integrations.
Today, I have something like this in the integration test class:
#SpringBootTest
#WebAppConfiguration
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
#RunWith(SpringRunner.class)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderIT {
And the fields and annotations to prepare my integration test:
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Autowired
private ObjectMapper mapper;
#MockBean
private SomeGateway someGateway;
#MockBean
private SomeRabbitMqService someRabbitMqService ;
#MockBean
private AnotherRabbitMqService anotherRabbitMqService;
#MockBean
private SomeIntegrationService someIntegrationService ;
#MockBean
private Clock clock;
#Before
public void setup() {
//some methods mocking each service above, preparing mockMvc, etc
}
This scenario is necessary for use the MockMvc and create the main feature in the system, my Order. This Order is created by calling a POST method in a Rest API, saving the order in a memory database.
Even this working well, I need to duplicate this block of code containing these #MockBean and some #Autowired in another tests, because the Order is the base scenario to add Products to the order, set an Address to deliver, etc. Each scenario has a different integration test but all of them needs an Order.
So, how to share the "MockBeans" and the methods that mocks them among my Integration Tests? I had really bad experiences using inheritance among the tests and I really would like to try a different approach.

I end up using the Spring profiles.
I created a configuration class annotated with #Profile("test") and created the mocked beans there. Like:
#Profile("test")
#Configuration
public class MyMockConfiguration {
#Bean
public SomeService someService() {
SomeService someService = mock(SomeService .class);
// mocked methods and results
return someService ;
}
And in the Test class:
#ActiveProfiles("test")
#SpringBootTest
#WebAppConfiguration
#RunWith(SpringRunner.class)
public class MyControllerIT {
If some integration test needs to override the current mock implementation on the profile, the test just needs to declare #MockBean on the class and proceed on the mock as usual.
I'm not decide yet if test is a good name, because for me makes more sense mock the configuration by "context". So, instead of use the generic name test on the profile name, I could use createOrder and have different configuration profiles, each one with a different name and different mocks: createOrder, createOrderWithoutProducts.

I believe #ContextConfiguration was created for this purpose: https://spring.io/blog/2011/06/21/spring-3-1-m2-testing-with-configuration-classes-and-profiles

Related

Write Unit test in SpringBoot Without start application

Am developing MicroServices in springBoot. Am writing unit test for Service and DAO layer. When I use #SpringBootTest it starting application on build. But It should not start application
when I run unit test. I used #RunWith(SpringRunner.class), But am unable to #Autowired class instance in junit class. How can I configure junit test class that should not start application and how to #Autowired class instance in junit class.
Use MockitoJUnitRunner for JUnit5 testing if you don't want to start complete application.
Any Service, Repository and Interface can be mocked by #Mock annotation.
#InjectMocks is used over the object of Class that needs to be tested.
Here's an example to this.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
public class AServiceTest {
#InjectMocks
AService aService;
#Mock
ARepository aRepository;
#Mock
UserService userService;
#Before
public void setUp() {
// MockitoAnnotations.initMocks(this);
// anything needs to be done before each test.
}
#Test
public void loginTest() {
Mockito.when(aRepository.findByUsername(ArgumentMatchers.anyString())).thenReturn(Optional.empty());
String result = aService.login("test");
assertEquals("false", result);
}
With Spring Boot you can start a sliced version of your application for your tests. This will create a Spring Context that only contains a subset of your beans that are relevant e.g. only for your web layer (controllers, filters, converters, etc.): #WebMvcTest.
There is a similar annotation that can help you test your DAOs as it only populates JPA and database relevant beans (e.g. EntitiyManager, Datasource, etc.): #DataJpaTest.
If you want to autowire a bean that is not part of the Spring Test Context that gets created by the annotatiosn above, you can use a #TestConfiguration to manually add any beans you like to the test context
#WebMvcTest(PublicController.class)
class PublicControllerTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
static class TestConfig {
#Bean
public EntityManager entityManager() {
return mock(EntityManager.class);
}
#Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
}
Depending your test setup, if you don't want to autowire a mock but the "real thing", You could simply annotate your test class to include exactly the classes you need (plus their transitive dependencies if necessary)
For example :
#SpringJUnitConfig({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#Import({ SimpleMeterRegistry.class })
or
#SpringJUnitConfig
#ContextConfiguration(classes = { SimpleMeterRegistry.class })
See working JUnit5 based samples in here Spring Boot Web Data JDBC allin .

how to use junit and mockito when we have a mulit layer service call approach in a restfull web service

i am using spring tool suite to write a code .there are 4 layers restContoller,buisnesslogic,domain ,service....
i want to test for a method of business logic layer where it calls a method of dao which finally calls a method of service layer to return a simple primitive value... to make it clear in the businesslogic class i have autowired domain class ,and in the domain class i have autowired the service classs..the problem that i am facing iss when i run the test class i am getting NullPointerException i am attaching the code for the test class... kindly help if possible
#ExtendWith(MockitoExtension.class)
class CustomerBlTest {
#Mock
CustomerService mockService;
#Autowired
CustomerDO customerDo;
#Autowired
#InjectMocks
CustomerBl bl; //buisnesslogic class
#Test
void checkForGetInteger() {
when(mockService.getIntegerFfromService()).thenReturn(3);
int actual = bl.getInteger();
Assertions.assertEquals(3, actual);
}
}
Since you are extending MockitoExtension hence this test class is not aware of spring. But you are still using #Autowired annotation. So that's wrong. Remove all #AUtowired annotations in the test class. Besides this you do not need to bring in all the sterotyped classes. Bring in only the one that the class is using i.e. in your case the classes injected in CustomerBl class. I think that should be CustomerService class. So remove the CustomerDO class if it's not being used in CustomerBl class. The #InjectMock and #MOck annotation have been applied correclty. I think that should help you get your result.
You need to use #Mock instead of #Autowired as shown below.
#ExtendWith(MockitoExtension.class)
class CustomerBlTest {
#Mock
CustomerService mockService;
#Mock
CustomerDO customerDo;
#InjectMocks
CustomerBl bl; //buisnesslogic class
#Test
void checkForGetInteger() {
when(mockService.getIntegerFfromService()).thenReturn(3);
int actual = bl.getInteger();
Assertions.assertEquals(3, actual);
}
}

How to inject Mocks into Spring Service

Environment :
Spring MVC 4
Junit
Mockito
Code :
Spring Service under test :
#Service("abhishekService")
public class AbhishekServiceImpl implements AbhisheskService {
#Autowired
private DaoOne daoOne;
#Autowired
private DaoTwo daoTwo;
#Autowired
private DaoThree daoThree;
#Autowired
private DaoFour daoThree;
}
Junit Test :
public class AbhishekServiceImplTest {
#Mock
private DaoOne daoOne;
#Mock
private DaoTwo daoTwo;
#Mock
private DaoThree daoThree;
#Mock
private UserDao userDao;
private AbhisheskService abhisheskService;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
abhisheskService = new AbhishekServiceImpl();
}
}
Issue :
1)As shown in code snippet one , the class under test uses four dependencies.
2)As shown in code snippet two , in junit test case class , all 4 dependencies are mocked using #Mock
3)My question is : how these four mocked objects should be injected into test class ?
4)My class under test doesn't have constructor/setter injection but field injection using #Autowired.
5)I don't want to use #InjectMocks annotation due to its dangerous behavior
as mentioned here
Can anybody please guide on this ?
You are trying to test a class wrongly designed to test the behavior i.e. the properties are not accessible to be mocked. AbhishekServiceImpl has to provide a way to inject the mocks to the class. If you cannot access the fields then it is a clear case of wrongly designed class. Considering that the AbhishekServiceImpl is a class in a legacy code and you are trying to test the behaviour then you can use reflection to inject the mock objects as below:
DaoOne mockedDaoOne = mock(DaoOne.class);
when(mockedDaoOne.doSomething()).thenReturn("Mocked behaviour");
AbhishekService abhishekService = new AbhishekServiceImpl();
Field privateField = PrivateObject.class.getDeclaredField("daoOne");
privateField.setAccessible(true);
privateField.set(abhishekService, mockedDaoOne);
assertEquals("Mocked behaviour", abhishekService.doSomething());
Its very rare that you test behaviour of a class that you have not written yourself. Though I can imagine a use case where you have to test an external library because its author did not test it.
You can mark the junit test with #RunWith(SpringJUnit4ClassRunner.class) and then use #ContextConfiguration to define a context which instantiates the DAOs and service and wires them together.

Trying to generate error about Spring's #Autowired field injection for JUnit

I am working with Spring 4.0.7, and with JUnit about testing of course.
About DI Spring offers #Autowired to be used in three locations
constructor
setter
field
I always work through the two first, why never the third option?
Because I remember have read long time ago about field injection should not be used, because it has a negative consequence about Testing. It does JUnit fails.
Note: Only for Testing is the problem. To runtime or production all goes well
Objective: For demonstration/academic purposes I want generate this problem.
I have the following:
Repository
public interface PersonRepository extends JpaRepository<Person, String>{
}
A Service
#Service
#Transactional
#Profile("failure")
public class PersonFailTestServiceImpl implements PersonService {
private static final Logger logger = ...
#Autowired
private PersonRepository personRepository;
Other Service (calling or using the service shown above)
#Service
#Transactional
#Profile("failure")
public class PersonFailTestProcessImpl implements PersonProcess {
private static final Logger logger = ...
#Autowired
private PersonService personService;
How you can see the two services are based on Field Injection.
Now the testing:
How the beans are loaded
#Configuration
#ComponentScan( basePackages={"com.manuel.jordan.server.infrastructure"},
basePackageClasses={PersonProcess.class,PersonRepository.class, PersonService.class})
public class CentralConfigurationEntryPoint {
}
#ContextConfiguration(classes=CentralConfigurationEntryPoint.class)
public class CentralTestConfigurationEntryPoint {
}
Now the two testing classes
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles({"development","failure"})
public class PersonServiceImplDevelopmentFailureTest extends CentralTestConfigurationEntryPoint {
#Autowired
private PersonService personService;
#Test
public void savePerson01(){
Person person01 = PersonFactory.createPerson01();
personService.save(person01);
personService.printPerson(personService.findOne("1"));
}
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles({"development","failure"})
public class PersonProcessImplDevelopmentFailureTest extends CentralTestConfigurationEntryPoint{
#Autowired
private PersonProcess personProcess;
Well all the testing methods pass, all green. I don't know if I am missing something or through Spring 4 the problem has been fixed
If this was your premise or problem
Because I remember have read long time ago about field injection
should not be used, because it has a negative consequence about
Testing. It does JUnit fails.
then you thought wrong. There is nothing inherently wrong with using field injection, definitely nothing that would cause JUnit tests to fail in and of itself. If a bean exists, Spring will be able to inject it whether it's in a constructor, a setter method, or a field.
Since you've activated your failure profile, your PersonFailTestServiceImpl bean will be found.
I think I can help. The example code you've posted here is a good example of a system / integration test, not a UNIT test.
If you were UNIT testing PersonFailTestProcessImpl, you would have to set the personRepository dependency yourself through code. But it is private, so how do you do this? You cannot use a constructor or setter since none is provided. This is what is meant by 'hard to unit test'.
Java 5+ provides a way to set private variables like this via reflection (the so-called privileged accessor). Basically, you obtain the class, get the declared field, call its setAccessible method, then you can set its value directly. There are libraries that will do these steps for you, but the point is that this is a pain compared to X.setSomething();
So there is nothing that 'makes jUnit fails' by using #Autowired on a private field. But building an object model without constructors or setters for establishing dependencies is unnecessarily constraining.

Reuse spring application context across junit test classes

We've a bunch of JUnit test cases (Integration tests) and they are logically grouped into different test classes.
We are able to load Spring application context once per test class and re-use it for all test cases in a JUnit test class as mentioned in http://static.springsource.org/spring/docs/current/spring-framework-reference/html/testing.html
However, we were just wondering if there is a way to load Spring application context only once for a bunch of JUnit test classes.
FWIW, we use Spring 3.0.5, JUnit 4.5 and use Maven to build the project.
Yes, this is perfectly possible. All you have to do is to use the same locations attribute in your test classes:
#ContextConfiguration(locations = "classpath:test-context.xml")
Spring caches application contexts by locations attribute so if the same locations appears for the second time, Spring uses the same context rather than creating a new one.
I wrote an article about this feature: Speeding up Spring integration tests. Also it is described in details in Spring documentation: 9.3.2.1 Context management and caching.
This has an interesting implication. Because Spring does not know when JUnit is done, it caches all context forever and closes them using JVM shutdown hook. This behavior (especially when you have a lot of test classes with different locations) might lead to excessive memory usage, memory leaks, etc. Another advantage of caching context.
To add to Tomasz Nurkiewicz's answer, as of Spring 3.2.2 #ContextHierarchy annotation can be used to have separate, associated multiple context structure. This is helpful when multiple test classes want to share (for example) in-memory database setups (datasource, EntityManagerFactory, tx manager etc).
For example:
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("FirstTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
...
}
#ContextHierarchy({
#ContextConfiguration("/test-db-setup-context.xml"),
#ContextConfiguration("SecondTest-context.xml")
})
#RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
...
}
By having this setup the context that uses "test-db-setup-context.xml" will only be created once, but beans inside it can be injected to individual unit test's context
More on the manual: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management (search for "context hierarchy")
One remarkable point is that if we use #SpringBootTests but again use #MockBean in different test classes, Spring has no way to reuse its application context for all tests.
Solution is to move all #MockBean into an common abstract class and that fix the issue.
#SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {
#MockBean
private ProductService productService;
#MockBean
private InvoiceService invoiceService;
}
Then the test classes can be seen as below
public class ProductControllerIT extends AbstractIT {
// please don't use #MockBean here
#Test
public void searchProduct_ShouldSuccess() {
}
}
public class InvoiceControllerIT extends AbstractIT {
// please don't use #MockBean here
#Test
public void searchInvoice_ShouldSuccess() {
}
}
Basically spring is smart enough to configure this for you if you have the same application context configuration across the different test classes. For instance let's say you have two classes A and B as follows:
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {
#MockBean
private C c;
//Autowired fields, test cases etc...
}
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {
#MockBean
private D d;
//Autowired fields, test cases etc...
}
In this example class A mocks bean C, whereas class B mocks bean D. So, spring considers these as two different configurations and thus would load the application context once for class A and once for class B.
If instead, we'd want to have spring share the application context between these two classes, they would have to look something as follows:
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {
#MockBean
private C c;
#MockBean
private D d;
//Autowired fields, test cases etc...
}
#ActiveProfiles("h2")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {
#MockBean
private C c;
#MockBean
private D d;
//Autowired fields, test cases etc...
}
If you wire up your classes like this, spring would load the application context only once either for class A or B depending on which class among the two is ran first in the test suite. This could be replicated across multiple test classes, only criteria is that you should not customize the test classes differently. Any customization that results in the test class to be different from the other(in the eyes of spring) would end up creating another application context by spring.
create your configuaration class like below
#ActiveProfiles("local")
#RunWith(SpringJUnit4ClassRunner.class )
#SpringBootTest(classes ={add your spring beans configuration classess})
#TestPropertySource(properties = {"spring.config.location=classpath:application"})
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {
private ClassLoader classloader = Thread.currentThread().getContextClassLoader();
private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);
//auto wire all the beans you wanted to use in your test classes
#Autowired
public XYZ xyz;
#Autowired
public ABC abc;
}
Create your test suite like below
#RunWith(Suite.class)
#Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {
private ClassLoader classloader = Thread.currentThread().getContextClassLoader();
private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);
}
Create your test classes like below
public class Test1 extends RunConfigration {
#Test
public void test1()
{
you can use autowired beans of RunConfigration classes here
}
}
public class Test2a extends RunConfigration {
#Test
public void test2()
{
you can use autowired beans of RunConfigration classes here
}
}

Resources