I have a class like this
public abstract class JobProcessor {
#Autowired
ApplicationContext applicationContext;
protected void startProcess() {
MyThread myThread= (MyThread) applicationContext.getBean("myThread");
myThread.setConversionObject(new MyObject());
...
}
}
I want to write unit test for JobProcessor. JobProcessor is an abstract class.and it is autowired with ApplicationContext which is an interface.
My test is like this
#SpringBootTest(classes = JobProcessorApplication.class)
#ContextConfiguration(locations = "classpath:InjectionContext.xml")
#TestPropertySource(locations = "classpath:test.properties")
#Import(UnitTestConfiguration)
class JobProcessorSpec extends Specification {
class JobProcessorChild extends JobProcessor {
#Override
boolean processRequest() {
return false
}
def "Should start process"() {
given:
def jobProcessorChild = new JobProcessorChild()
when:
jobProcessorChild.startProcess()
then:
noExceptionThrown()
}
}
}
My test always fails for nullpoint error of applicationContext
Can anyone please guide me how to write unit test properly here?
Unit tests should run without applicationContext being built. You have to replace it with a mock object which you will then pass into your object's constructor in the test.
Related
I have an aspect class as below -
public class TestAspect {
#AfterThrowing(pointcut = "execution(* *(..)) ", throwing = "testException")
public void afterThrowAdvice(TestException testException) throws Exception {
}
}
Now anytime any class throws TestException, TestAspect's afterThrowAdvice method is getting called. From Unit tests as well without using any spring configuration xmls, running as a plain junit test. How do I mock to not do anything when that method is called? I tried in my unit test the below but it won't work. Rightly so because the aspect is not autowired into the classes I am testing. Any suggestions? -
#Mock
private TestAspect testAspect
doNothing.when(testAspect).afterThrowAdvice(any(TestException.class));
One way to achieve this is using #Profile option .
Following profile configuration will make sure the aspect bean is only available with the profile is not test
#Component
#Aspect
#Profile("!test")
public class TestAspect {
#AfterThrowing(pointcut = "execution(* *(..)) ", throwing = "testException")
public void afterThrowAdvice(TestException testException) throws Exception {
...
}
}
and following Junit testcase runs the the tests with profile as test and no aspect bean is created here
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestExConfig.class)
#ActiveProfiles("test")
public class TestService{
#Autowired
ServiceWithException service;
#Test(expected = TestException.class)
public void testExceptionMethod() throws TestException {
service.testMethod();
}
}
Hope this helps
I have one service class that I want to mock but while running the test I am Getting Caused by: java.lang.IllegalStateException: Duplicate mock definition [MockDefinition#482ba4b1 name = '', typeToMock = com.service.ThirdPartyService, extraInterfaces = set[[empty]], answer = RETURNS_DEFAULTS, serializable = false, reset = AFTER]
I have tried to create mock service using #MockBean at class level, field level, and used #Qualifier as well to resolve the issue
#Service
public class ThirdPartyService{
.......................
public String decrypt(String encryptedText) {
//third party SDK I am using
return Service.decrypt.apply(encryptedText);
}
.........
..............
}
#ComponentScan("com")
#PropertySource({"classpath:/api.properties", "classpath:/common.properties"})
#SpringBootConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#Transactional
public class TestControllerTest extends IntegrationTest {
#MockBean
ThirdPartyService thirdPartyService;
#Before
public void initMocks(){
MockitoAnnotations.initMocks(this);
}
#Test
public void test() throws Exception {
when(ts.decrypt("encryptedText")).thenReturn("decryptedText")
Request req = Request.builder().name("name123").build();
//written performPost method in some other class
ResultActions action = performPost("/test", req);
action.andExpect(status().isOk());
}
}
public class IntegrationTest {
protected final Gson mapper = new Gson();
private MockMvc mvc;
#Autowired
private WebApplicationContext context;
public ObjectMapper objectMapper = new ObjectMapper();
#Before
public void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}}
When I am calling Thirdparty service decrypt method then it should return me decryptedText as a string. But getting duplicate mock definition error
I had the same issue.
Cause of this were test configuration file which was put somewhere else and it contained the mocked bean.
I have solved this by using #Autowired instead of #MockBean as this will result in autowiring the already mocked bean.
In my case the problem appeared after another dependency update and the reason was in the #SpringBootTest annotation referencing the same class twice:
#SpringBootTest(classes = {MyApplication.class, ApiControllerIT.class})
class ApiControllerIT extends IntegrationTestConfigurer {
// ...
}
#SpringBootTest(classes = {MyApplication.class, TestRestTemplateConfiguration.class})
public class IntegrationTestConfigurer {
// ...
}
I fixed it by removing #SpringBootTest annotation from the child class (ApiControllerIT).
In my case it was incorrect test class name that doesn't end with 'Test'.
If you have nested test classes try this:
#NestedTestConfiguration(OVERRIDE)
From Spring release notes: https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x#upgrading-to-version-53
Let's use the following as an example.
#Autowired
#MockBean
private Foo foobar;
Does the Spring Context load the class Foo first, and then apply the mock? Or does the #Mockbean get detected somehow, and Spring creates and applies the mock instead of loading the class Foo into the Spring Context. I have a suspicion that it is the latter, but I would like confirmation.
Spring will throw an exception.
Let's define the class Foo.
#Component
public class Foo {
public Foo() {
System.out.println("I am not a mock");
}
}
Whenever testing using #Autowired, spring injects an instance of Foo and the constructor will print "I am not a mock", as in the code below.
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
#Autowired
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
On the other hand, using #MockBean, spring will not create the real bean and the message in the constructor will not be printed. This scenario is represented by the following code.
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
#MockBean
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
However, when you try to use both annotations together, spring will throw a BeanCreationException caused by an IllegalStateException. It means that the field foo cannot have an existing value. This scenario will happen in executing the code below:
#SpringBootTest(classes = Main.class)
#RunWith(SpringRunner.class)
public class FooTest {
// this will not work
#Autowired
#MockBean
Foo foo;
#Test
public void test() {
System.out.println(foo);
}
}
And the stack trace will be something like:
org.springframework.beans.factory.BeanCreationException: Could not inject field: com.tbp.Foo com.FooTest.foo; nested exception is java.lang.IllegalStateException: The field com.tbp.Foo com.FooTest.foo cannot have an existing value
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.inject(MockitoPostProcessor.java:413) ~[spring-boot-test-1.5.2.RELEASE.jar:1.5.2.RELEASE]
I am looking for a way to mock a Service bean used in Controller so I can test only controller using MockMvc. But I can't find an easy way to replace real bean with Spock mock. Everything uses spring-boot 1.3.2 version. More details below:
I have a following controller class
#RestController
#RequestMapping(path = "/issues")
#AllArgsConstructor(onConstructor = #__(#Autowired))
public class NewsletterIssueController {
private final GetLatestNewsletterIssueService latestNewsletterIssueService;
#RequestMapping(
method = RequestMethod.GET,
path = "/latest"
)
public ResponseEntity getLatestIssue() {
Optional<NewsletterIssueDto> latestIssue = latestNewsletterIssueService.getLatestIssue();
if (latestIssue.isPresent()) {
return ResponseEntity.ok(latestIssue.get());
} else {
return ResponseEntity.notFound().build();
}
}
}
And Integration Spock test for this class:
#ContextConfiguration(classes = [Application], loader = SpringApplicationContextLoader)
#WebAppConfiguration
#ActiveProfiles("test")
class NewsletterIssueControllerIntegrationSpec extends Specification {
MockMvc mockMvc
#Autowired
GetLatestNewsletterIssueService getLatestNewsletterIssueService
#Autowired
WebApplicationContext webApplicationContext
def setup() {
ConfigurableMockMvcBuilder mockMvcBuilder = MockMvcBuilders.webAppContextSetup(webApplicationContext)
mockMvc = mockMvcBuilder.build()
}
def "Should get 404 when latest issue does not exist"() {
given:
getLatestNewsletterIssueService.getLatestIssue() >> Optional.empty() // this won't work because it is real bean, not a Mock
expect:
mockMvc.perform(MockMvcRequestBuilders
.get("/issues/latest")
.contentType(JVM_BLOGGERS_V1)
.accept(JVM_BLOGGERS_V1)
).andExpect(MockMvcResultMatchers.status().isNotFound())
}
}
I need a way to replace this autowired bean with a Mock/Stub so I can define interactions in 'given' section.
I'd create a local configuration in the test and override the bean there.
I don't know Groovy, but it would like this in Java:
#ContextConfiguration(classes = NewsletterIssueControllerIntegrationSpec.Conf.class, loader = SpringApplicationContextLoader.class)
#WebAppConfiguration
#ActiveProfiles("test")
class NewsletterIssueControllerIntegrationSpec extends Specification {
#Configuration
#Import(Application.class)
public static class Conf {
#Bean
public GetLatestNewsletterIssueService getLatestNewsletterIssueService() {
return mock(GetLatestNewsletterIssueService.class);
}
}
// […]
}
Caveat: This approach works well with Mockito, but you might need a pre-release version of Spock for it to work, ref: https://github.com/spockframework/spock/pull/546
By the way: Spring Boot 1.4 will provide a #MockBean construction to simplify this.
With Spock 1.2 you can use SpringBean annotation to inject mocked service in spring context https://objectpartners.com/2018/06/14/spock-1-2-annotations-for-spring-integration-testing/
So with this new annotation your test would be :
#WebMvcTest(controllers = [NewsletterIssueController], secure = false)
#AutoConfigureMockMvc
#ActiveProfiles("test")
class NewsletterIssueControllerIntegrationSpec extends Specification {
#Autowired
MockMvc mockMvc
#SpringBean
GetLatestNewsletterIssueService getLatestNewsletterIssueService
def setup() {
// nothing to do as SpringBean and WebMvcTest do the job for you
}
def "Should get 404 when latest issue does not exist"() {
given:
getLatestNewsletterIssueService.getLatestIssue() >> Optional.empty() // this won't work because it is real bean, not a Mock
expect:
mockMvc.perform(MockMvcRequestBuilders
.get("/issues/latest")
.contentType(JVM_BLOGGERS_V1)
.accept(JVM_BLOGGERS_V1)
).andExpect(MockMvcResultMatchers.status().isNotFound())
}
}
I'm being hit with the issue that spock doesn't allow Mocks to be created outside of the specification - How to create Spock mocks outside of a specification class?
This seems to be still outstanding so am asking is that giving that i've got a complex and nested DI graph what is the most efficient way to 'inject' a mock representation deep in the graph?
Ideally, I have one bean definition set for normal deployment and another when running unit tests and it is this definition set being the applicable Mocks
e.g.
#Configuration
#Profile("deployment")
public class MyBeansForDeployment {
#Bean
public MyInterface myBean() {
return new MyConcreateImplmentation();
}
}
&&
#Configuration
#Profile("test")
public class MyBeansForUnitTests {
#Bean
public MyInterface myBean() {
return new MyMockImplementation();
}
}
Since Spock 1.1, you can create mocks outside of a specification class (detached mocks). One of the options is DetachedMockFactory. Take a look at the documentation or my answer to the question you linked.
You could try to implement a BeanPostProcessor that will replace the beans that you want with test doubles, such as shown below:
public class TestDoubleInjector implements BeanPostProcessor {
...
private static Map<String, Object[]> testDoubleBeanReplacements = new HashMap<>();
public void replaceBeanWithTestDouble(String beanName, Object testDouble, Class testDoubleType) {
testDoubleBeanReplacements.put(beanName, new Object[]{testDouble, testDoubleType});
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (testDoubleBeanReplacements.containsKey(beanName)) {
return testDoubleBeanReplacements.get(beanName)[TEST_DOUBLE_OBJ];
}
return bean;
}
In your test, setup your mocks like shown below before initializing the application context. Make sure to include the TestDoubleInjector as a bean in your test context.
TestDoubleInjector testDoubleInjector = new TestDoubleInjector()
testDoubleInjector.replaceBeanWithTestDouble('beanToReplace', mock(MyBean.class), MyBean.class)
It could be done using HotSwappableTargetSource
#WebAppConfiguration
#SpringApplicationConfiguration(TestApp)
#IntegrationTest('server.port:0')
class HelloSpec extends Specification {
#Autowired
#Qualifier('swappableHelloService')
HotSwappableTargetSource swappableHelloService
def "test mocked"() {
given: 'hello service is mocked'
def mockedHelloService = Mock(HelloService)
and:
swappableHelloService.swap(mockedHelloService)
when:
//hit endpoint
then:
//asserts
and: 'check interactions'
interaction {
1 * mockedHelloService.hello(postfix) >> { ""Mocked, $postfix"" as String }
}
where:
postfix | _
randomAlphabetic(10) | _
}
}
And this is TestApp (override the bean you want to mock with proxy)
class TestApp extends App {
//override hello service bean
#Bean(name = HelloService.HELLO_SERVICE_BEAN_NAME)
public ProxyFactoryBean helloService(#Qualifier("swappableHelloService") HotSwappableTargetSource targetSource) {
def proxyFactoryBean = new ProxyFactoryBean()
proxyFactoryBean.setTargetSource(targetSource)
proxyFactoryBean
}
#Bean
public HotSwappableTargetSource swappableHelloService() {
new HotSwappableTargetSource(new HelloService());
}
}
Have a look at this example https://github.com/sf-git/spock-spring