Am correct in understanding that when using Spring, you should use the Spring configuration xml to instantiate your objects for production, and directly instantiate objects when testing?
Eg.
MyMain.java
package org.world.hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyMain {
private Room room;
public static void speak(String str)
{
System.out.println(str);
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Room room = (Room) context.getBean("myRoom");
speak(room.generatePoem());
}
}
Room.java
package org.world.hello;
public class Room {
private BottleCounter bottleCounter;
private int numBottles;
public String generatePoem()
{
String str = "";
for (int i = numBottles; i>=0; i--)
{
str = str + bottleCounter.countBottle(i) + "\n";
}
return str;
}
public BottleCounter getBottleCounter() {
return bottleCounter;
}
public void setBottleCounter(BottleCounter bottleCounter) {
this.bottleCounter = bottleCounter;
}
public int getNumBottles() {
return numBottles;
}
public void setNumBottles(int numBottles) {
this.numBottles = numBottles;
}
}
BottleCounter.java
package org.world.hello;
public class BottleCounter {
public String countBottle(int i)
{
return i + " bottles of beer on the wall" + i + " bottles of beer!";
}
}
Beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="myRoom" class="org.world.hello.Room">
<property name="bottleCounter">
<bean id = "myBottleCounter" class = "org.world.hello.BottleCounter"/>
</property>
<property name = "numBottles" value = "10"></property>
</bean>
</beans>
Outputs: (my apologies for the missing space)
10 bottles of beer on the wall10 bottles of beer!
9 bottles of beer on the wall9 bottles of beer!
8 bottles of beer on the wall8 bottles of beer!
7 bottles of beer on the wall7 bottles of beer!
6 bottles of beer on the wall6 bottles of beer!
5 bottles of beer on the wall5 bottles of beer!
4 bottles of beer on the wall4 bottles of beer!
3 bottles of beer on the wall3 bottles of beer!
2 bottles of beer on the wall2 bottles of beer!
1 bottles of beer on the wall1 bottles of beer!
0 bottles of beer on the wall0 bottles of beer!
Now for testing this:
BottleCounterTest.java:
package org.world.hello;
import static org.junit.Assert.*;
import org.junit.Test;
public class BottleCounterTest {
@Test
public void testOneBottle() {
BottleCounter b = new BottleCounter();
assertEquals("1 bottles of beer on the wall1 bottles of beer!", b.countBottle(1));
}
}
Pretty straight forward.
RoomTest.java:
package org.world.hello;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import org.junit.Test;
public class RoomTest {
@Test
public void testThreeBottlesAreSeperatedByNewLines()
{
Room r = new Room();
BottleCounter b = Mockito.mock(BottleCounter.class);
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
r.setBottleCounter(b);
r.setNumBottles(3);
assertEquals("a\na\na\na\n", r.generatePoem());
}
}
Am I correct in instantiating my test objects this way?
Inner static class configuration:
When testing Spring components we usually use @RunWith(SpringJUnit4ClassRunner.class)
and make our class @ContextConfiguration
. By making class @ContextConfiguration
you can create an inner static class for configuration and in it you have full control. There you define all you need as beans and @Autowired
it in your test, along with dependencies which can be mocks or regular objects, depending on test case.
Component scanning production code:
If there are more components needed for test you can add @ComponentScan
but we try to make it scan only packages it needs (this is when you use @Component
annotation but in your case you can add XML to @ContextConfiguration
). This is a good choice when you do not need mocks and you have a complicated setup which needs to be production like. This is good for integration tests where you want to test how components interact with each other in functional slices you want to test.
Hybrid approach: This is the usual case when you have many beans which need to be production like but one or two need to be mocks. Then you can @ComponentScan
production code but add an inner static class which is @Configuration
and there define beans with annotation @Primary
which will override production code configuration for that bean in case of tests. This is good since you do not need to write long @Configuration
with all defined beans, you scan what you need and override what should be mocked.
In your case I would go with first approach like this:
package org.world.hello;
import static org.junit.Assert.*;
import org.mockito.Mockito;
import org.junit.Test;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class RoomTest {
@Configuration
//@ImportResource(value = {"path/to/resource.xml"}) if you need to load additional xml configuration
static class TestConfig {
@Bean
public BottleCounter bottleCounter() {
return Mockito.mock(BottleCounter.class);
}
@Bean
public Room room(BottleCounter bottleCounter) {
Room room = new Room();
room.setBottleCounter(bottleCounter);
//r.setNumBottles(3); if you need 3 in each test
return room;
}
}
@Autowired
private Room room; //room defined in configuration with mocked bottlecounter
@Test
public void testThreeBottlesAreSeperatedByNewLines()
{
Mockito.when(b.countBottle(Mockito.anyInt())).thenReturn("a");
r.setNumBottles(3);
assertEquals("a\na\na\na\n", r.generatePoem());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With