I'm using Spring annotation based configuration in my Play application.
Controllers and DAOs are Spring beans. Controller and DAO layers are defined with different Spring profiles and each layer could be disabled separately.
I'd like to test controller layer in isolation from DAO layer. I've disabled DAO profile and redefined each of DAO beans as a Mockito mock. From functional point of view it works fine, the only thing I don't like is defining mocks manually like this:
#Configuration
#Import(AppContext.class)
public class TestAppContext {
#Bean
public DaoA getDaoA(){
return mock(DaoA.class);
}
//... all dependencies are re-defined manually
}
Is there a way to define package (like with #ComponentScan annotation)
and get all beans in that package as mocks instead of real objects?
UPD:
I'm running tests with FakeApplication (https://www.playframework.com/documentation/2.0/api/java/play/test/FakeApplication.html), so context is started not in the test level, but inside fake application startup.
public class ControllerTest extends WithApplication {
#Before
public void setUp() throws Exception {
start(fakeApplication(new GlobalSettings(){
private ApplicationContext appContext;
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
}
#Override
public <A> A getControllerInstance(Class<A> clazz) throws Exception {
return appContext.getBean(clazz);
}
}));
}
...
}
I did it like this because I wan't to make the test more reliable and test how controller works in real environment:
#Test
public void testControllerMethod() {
Result result = route(fakeRequest(GET, "/controller/method"));
assertThat(result).is(...);
}
If the number of dependencies you need to mock is huge, you can also use spring-auto-mock.
#ContextConfiguration(classes = { AutoMockRegistryPostProcessor.class, RestOfClasses.class, ... })
#RunWith(SpringJUnit4ClassRunner.class)
public class YourTest {
...
}
As you are creating the ApplicationContext on your own, you can register the postprocessor programmatically:
public void onStart(Application app) {
appContext = new AnnotationConfigApplicationContext(TestAppContext.class);
appContext.getBeanFactory().addBeanPostProcessor(new AutoMockRegistryPostProcessor())
}
Mark your unit-test with #RunWith(SpringJUnit4ClassRunner.class)
Mark your tested class as #InjectMock
Mark you Dao class as #Mock
Make use of Mockito in your project
Related
I am working on creating integration test for a service class i am testing and I needed to mock the dao for one of the test methods. the problem is when i run the tests together some of my tests fail but when i run them individually the tests past. If i remove the mockito part all my tests pass when i run them all at once. any insight on this is appreciated
below is my code:
// here is my Service class
public class Service {
Dao dao;
public Dao getDao() {
return dao;
}
public void setDao(Dao dao) {
this.dao = dao;
}
}
//here is my integ test
#Category(IntegrationTest.class)
#RunWith(SpringRunner.class)
public class Test{
#Rule
public ExpectedException thrown = ExpectedException.none();
#Autowired
#Qualifier(Service.SERVICE_NAME)
protected Service service;
#Before
public void setUp() {
assertNotNull(service);
}
#Test
public void testDoSomethingOne() throws Exception {
Dao dao = Mockito(Dao.class)
service.setDao(dao)
boolean flag = service.doSomething();
Assert.assertTrue(flag);
}
#Test
public void testDoSomethingTwo() throws Exception {
Integer num = service.doSomething();
Assert.assertNotNull(num);
}
The test method testDoSomethingOne() sets the mock dao for the service instance which it retains for rest of the tests.
Annotate the method testDoSomethingOne() with #DirtiesContext to get a fresh context associated with the subsequent test method.
Test annotation which indicates that the ApplicationContext associated
with a test is dirty and should therefore be closed and removed from
the context cache.
Use this annotation if a test has modified the
context — for example, by modifying the state of a singleton bean,
modifying the state of an embedded database, etc. Subsequent tests
that request the same context will be supplied a new context.
You can get the dao before each test and assign it back to service after the test
something like this:
private static Dao dao;
#Before
public void setUp() {
if(dao == null) {
dao = service.getDao();
}
}
#After
public void tearDown() {
service.setDao(dao);
}
If it is a integration test you should not mock your daos, the recommended way is to use a in memory database like H2. The spring folks already provide the annotation #DataJpaTest that creates the database for you.
You can use the #DataJpaTest annotation to test JPA applications. By default, it scans for #Entity classes and configures Spring Data JPA repositories. If an embedded database is available on the classpath, it configures one as well. Regular #Component beans are not loaded into the ApplicationContext.
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test
I have a spring boot application and some other components which application should interact with. However, in my unit testing I am using just application functionality and I would like to mock outer API calls. I am stuck as I can't find the way to mock case like this:
My start class with main method:
#ComponentScan("com.sample.application")
#SpringBootApplication
public class MyApp implements CommandLineRunner {
#Autowired
private OuterAPI outerAPI;
public static void main(String[] args) {
SpringApplication.run(AdRedirectorMain.class, args);
}
#Override
public void run(String... args) throws Exception {
outerAPI.createInstances();
}
...
}
And here is my test class example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApp.class)
public class MyAppTest {
// any tests
}
I am working with Spring Boot, JUnit, Mockito.
So, I am facing the problem - how could I avoid this method call createInstances() with Mockito, via reflection or in any other way.
Have a look at Mocking and spying beans in the Spring Boot documentation.
You can use #MockBean in your test class to replace an autowired bean with a Mockito mock instance.
You can use #MockBean http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
or you can define an interface that you OuterAPI implements then for your test you provide a dummy implementation that makes a dummy call instead of actual call to the outerAPI.createInstances();
Another option that you have is to have configuration class like this:
#Configuration
#Profile(value = {"yourtest-profile"})
public class TestConfiguration{
#Primary
#Bean
public OuterAPI outerAPI() {
return Mockito.mock(OuterAPI.class);
}
}
and put it under scr/test/java
We have a Spring based JUnit test class which is utilizing an inner test context configuration class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ServiceTest.Config.class)
public class ServiceTest {
#Test
public void someTest() {
...
#Configuration
#PropertySource(value = { "classpath:application.properties" })
#ComponentScan({ "..." })
public static class Config {
...
New functionalities have been recently introduced to the Service class, for which the concerned tests should be added to ServiceTest. However these would also require a different test context configuration class to be created (the internals of the existing Config class are fairly complex and change it to serve both old and new tests seems to be be extremely difficult if possible at all)
Is there a way to achieve that certain test methods in one test class would use one config class and other methods would use another? #ContextConfiguration seems to be applicable only on class level, so solution could be to create another test class for the new tests which would utilize its own context configuration class; but it would mean that the same Service class is being covered via two different test classes
With Aaron's suggestion of manually building the context I couldn't find any good examples so after spending some time getting it working I thought I'd post a simple version of the code I used in case it helps anyone else:
class MyTest {
#Autowired
private SomeService service;
#Autowired
private ConfigurableApplicationContext applicationContext;
public void init(Class<?> testClass) throws Exception {
TestContextManager testContextManager = new TestContextManager(testClass);
testContextManager.prepareTestInstance(this);
}
#After
public void tearDown() throws Exception {
applicationContext.close();
}
#Test
public void test1() throws Exception {
init(ConfigATest.class);
service.doSomething();
// assert something
}
#Test
public void test2() throws Exception {
init(ConfigBTest.class);
service.doSomething();
// assert something
}
#ContextConfiguration(classes = {
ConfigATest.ConfigA.class
})
static class ConfigATest {
static class ConfigA {
#Bean
public SomeService someService() {
return new SomeService(new A());
}
}
}
#ContextConfiguration(classes = {
ConfigBTest.ConfigB.class
})
static class ConfigBTest {
static class ConfigB {
#Bean
public SomeService someService() {
return new SomeService(new B());
}
}
}
}
I use these approaches when I'm have to solve this:
Manually build the context in a setup method instead of using annotations.
Move the common test code to a base class and extend it. That allows me to run the tests with different spring contexts.
A mix of the two above. The base class then contains methods to build spring contexts from fragments (which the extensions can override). That also allows me to override test cases which don't make sense or do extra pre/post work in some tests.
Keep in mind that annotations only solve generic cases. You'll have to replicate some or all of their work when you leave the common ground.
I have a class which contains a few service activator methods as follows:
#MessageEndpoint
public class TestService {
#ServiceActivator
public void setComplete(Message<String> message){
//do stuff
}
}
In the integration flow, one of the channels call one of these methods:
#Bean
public TestService testService() {
return new TestService();
}
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.handle("testService", "setComplete")
.handle(logger())
.get();
}
I'm writing a unit test for this flow and using Mockito for mcoking the service activator class:
#ContextConfiguration(classes = IntegrationConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class AppTest {
#Mock
private TheGateway startGateway;
#Mock
private TestService testrvice;
#Autowired
#Qualifier("testChannel")
DirectChannel testChannel;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test()
public void testMessageProducerFlow() throws Exception {
Mockito.doNothing().when(startGateway).execute("test");
startGateway.execute("test");
Mockito.verify(startGateway).execute("test");
TestChannel.send(new GenericMessage<>("test"));
Mockito.verify(testService).setComplete(new GenericMessage<>("test"));
}
}
When I don't mock the TestService, it executes the flow without issues.
Any guideance on how to Mock the Service activator class would be helpful.
UPDATE:
When I mock it (as shown in snippet above), it does not call the mocked object, instead executes the actual stuff, and the last line Mockito.verify(testService)... asserts that the mock testService was never called.
First of all you misunderstood how Spring Test Framework works.
#ContextConfiguration(classes = IntegrationConfig.class) loads the config as is without any modification and start an application context based on that config.
According to the first condition your .handle("testService", "setComplete") uses testService() #Bean not #Mock
Only after the test applicationContext startup all those #Mocks and #Autowireds start working.
In other words your mocking doesn't change anything in the original IntegrationConfig.
In the Framework with use reflection to retrieve some field of the particular bean to replace it with the mock. But it isn't so easy way.
I suggest you to distinguish the Integration and Service configuration and use two different classes for production and for testing. Something like this:
The testService() #Bean must be moved from the IntegrationConfig to the new #Configuration class for production.
The TestServiceConfig may look like this:
#Bean
public TestService testService() {
return Mockito.mock(new TestService());
}
And finally your AppTest should be modified like this:
#ContextConfiguration(classes = {IntegrationConfig.class, TestServiceConfig.class})
....
#Autowired
private TestService testrvice;
That's everything is just because the application context and unit test scopes are on the different levels.
There have been many questions regarding request scoped management in unit tests and mainly answer is to do not test the scope management, as its a Spring Framework task and it should take care that it works properly. So advice, for example, would be to replace the request scope with thread or prototype type scope in the XML configuration file.
For most of the tests its enough, there are no complaints about not registered "request" scope and tests are running fine. But I do have one case where it is not enough.
Consider following case:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
public void test1() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
I want to test MyService, which is request-scoped. MyComponent is request scope as well.
Variant A
If I replace the request scope with SimpleThreadScope, then in by both tests I would receive the same instance of MyService and MyComponent, so for example test2() could receive bad results from MyComponent as it could internally contain some internal "trash" from previous test1()
Variant B
If I replace request scope with prototype scope - I would get the case where my test methods are receiving different instances of MyComponent as MyService does - so I cannot perform any assertions on them.
So what I would need is kind of test method-related request scope, where all request-scoped beans remain just during the test1() method and then gets destroyed, so within next test2() they would be created newly again.
Is it possible?
This may not be what you're looking for, but why are you using Spring to manage your test classes in the first place? That seems like overkill to me. For unit testing, you shouldn't need a DI container. Just mock the dependencies and focus on the encapsulated functionality of the Component or Service you're testing.
You won't be able to do that, though, while using field injection. You'll need to convert to constructor or method injection.
It is possible to receive a new context for every method in your unit test by annotating the method with #DirtiesContext. This allows you to manipulate beans in the application context for one unit test and not have it affect the others.
Documentation
Your example annotated with #DirtiesContext:
#Component
#Scope("request")
public class MyService {
#Autowired
private MyComponent component;
public void doSomething(String param) {
component.doTheThing(param);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
public class MyServiceTest {
#Autowired
private MyService service;
#Autowired
private MyComponent component;
#Test
#DirtiesContext
public void test1() {
service.doSomething("aaa");
assertEquals("AAA", component.getTheThing());
}
#Test
#DirtiesContext
public void test2() {
service.doSomething("bbb");
assertEquals("BBB", component.getTheThing());
}
}
If all of your test methods within your class require a new context you can also just annotate the class as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:my-scope-tweaks.xml"})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class MyServiceTest {
//Tests Here...
}