I have a #Service bean that I need static access to:
#Service
public class MyBean implements InitializingBean {
private static MyBean instance;
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
public static MyBean get() {
return instance;
}
public String someMethod(String param) {
return "some";
}
}
Usage:
#Service
public class OtherService {
public static void makeUse() {
MyBean myBean = MyBean.get();
}
}
Problem: when I write an integration junit test for OtherService that makes use of the stat MyBean access, the instance variable is always null.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ITest {
#Autowired
private OtherService service;
#MockBean
private MyBean myBean;
#Before
public void mock() {
Mockito.when(myBean.someMethod(any()).thenReturn("testvalue");
}
#Test
public void test() {
service.makeUse(); //NullPointerException, as instance is null in MyBean
}
}
Question: how can I write an integration test when using such type of static access to a spring-managed bean?
If you want to influence the #Bean-creation, then use #Configuration
#Configuration
public class MyConfig {
#Bean
public MyBean myBean() {
return new MyBean;
}
#Bean
public OtherService otherService () {
return new OtherService(myBean());
}
}
Then mocking becomes really easy:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ITest {
#MockBean
private MyBean myBean;
#Autowired
private OtherService service;
#Test
public void test() {
// service is initialised with myBean
...
}
}
When more control is needed, then you can choose the following approach. It provides sometimes more control than just a #MockBean. In your test you can easily mock a method just before calling it. This is my preferred way.
#Configuration
public class MyConfig {
...
#Bean
public MyBean getMyBean() {
return mock( MyBean.class);
}
#Bean
public OtherService otherService () {
return new OtherService( getMyBean());
}
}
In the application you can use #Autorwired to access it AND implement method overrule/mocking easily.
#RunWith(SpringRunner.class)
#SpringBootTest
public class AnotherTest {
#Autowired
private MyBean myBean;
#Autowired
private OtherService service;
#Test
public void test() {
when( myBean.method( a, b)).thenReturn( something);
service.doSomething( ....); // with myBean.method() overruled
}
}
Related
Is it possible to externalize #MockBean definitions, and combine them with composition instead of inheritance?
Because I find myself often repeating mock definitions, and I would rather be able to load/inject them and define them outside of the test class.
Example:
#SpringBootTest
public class Service1Test {
#MockBean
private Service1 service1;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
}
}
#SpringBootTest
public class Service2Test {
#MockBean
private Service2 service2;
#BeforeEach
public void mock() {
when(service2.call()).thenReturn(result2);
}
}
#SpringBootTest
public class Service3Test {
#MockBean
private Service1 service1;
#MockBean
private Service2 service2;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
when(service2.call()).thenReturn(result2);
}
}
I would like to externalize the mocks somehow, so I could just load them.
Pseudocode as follows:
public class MockService1 {
#MockBean
private Service1 service1;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
}
}
#SpringBootTest
#Import({MockService1.class, MockService2.class})
public class Service3Test {
}
Yes, you can achieve it by using an external configuration class. For example:
#TestConfiguration
public class TestConfig {
#Bean
#Primary
public Foo foo() {
return mock(Foo.class); //you can use Mockito or a different approach
here
}
#Bean
#Primary
public Bar bar() {
return mock(Foo.class);
}
}
#Import(TestConfig.class)
public class MyTestClass {
#Autowire
private Foo foo;
#Autowire
private Bar bar;
}
I have a service which caches the response. I use a simple Map for caching the response.
I also have two scopes: #RequestScope and #GrpcScope. #RequestScope is obviously for requests received from rest controller whereas #GrpcScope is for grpc requests.
My service doesn't know what scope it is currently running in. Both (grpc and rest) controllers uses the same service and there could be only one scope active at runtime.
I want FooService to use RequestScopedFooCache in if the the scope is #RequestScope and to use GrpcScopedFooCache if the scope is #GrpcScope. How can I do that?
// for RequestScope
#RestController
public class FooRestController{
#Autowired
FooService fooService;
#GetMapping
public BarResponse getFoo(){
return fooService.fooRequest(...)
}
}
// for #GrpcScope
#GrpcController
public class FooGrpcController{
#Autowired
FooService fooService;
public BarResponse getFoo(){
return fooService.fooRequest(...)
}
}
#Service
public class FooService {
#Autowired
FooCache cache; // doesn't know which bean to autowire however there could be only one of them at runtime
BarResponse fooRequest(String id) {
if(cache.get(id))
...
}
}
public interface FooCache {
...
}
#Component
#RequestScope
public class RequestScopedFooCache implements FooCache {
...
}
#Component
#GrpcScope
public class GrpcScopedFooCache implements FooCache {
...
}
Using SpringBoot version 2.4.+
Not exactly what you are asking for but I would probably create my own bean methods for FooService and give them qualifying names.
Changed FooService to take FooCache as a constructor:
public class FooService {
private final FooCache cache;
public FooService(FooCache cache) {
this.cache = cache;
}
BarResponse fooRequest(String id) {
if (cache.get(id))
...
}
}
Give the FooCache implementations qualifying names:
public interface FooCache {
...
}
#Component("GRPC_CACHE")
#RequestScope
public class RequestScopedFooCache implements FooCache {
...
}
#Component("REQUEST_CACHE")
#GrpcScope
public class GrpcScopedFooCache implements FooCache {
...
}
A bean manager to setup the FooService beans:
class FooServiceBeanManager {
#Bean("GRPC_SERVICE")
public FooService grpcFooService(#Qualifier("GRPC_CACHE") FooCache fooCache) {
return new FooService(fooCache);
}
#Bean("REQUEST_SERVICE")
public FooService requestFooService(#Qualifier("GRPC_CACHE") FooCache fooCache) {
return new FooService(fooCache);
}
}
Specify which bean you want in your controllers using #Qualifer:
// for RequestScope
#RestController
public class FooRestController {
private final FooService fooService;
#Autowired
public FooRestController(#Qualifier("REQUEST_SERVICE") FooService fooService) {
this.fooService = fooService;
}
#GetMapping
public BarResponse getFoo() {
return fooService.fooRequest(...)
}
}
// for #GrpcScope
#GrpcController
public class FooGrpcController {
private final FooService fooService;
#Autowired
public FooGrpcController(#Qualifier("GRPC_SERVICE") FooService fooService) {
this.fooService = fooService;
}
public BarResponse getFoo() {
return fooService.fooRequest(...)
}
}
I changed the controller classes to have #Autowired constructors instead of fields. It is the recommended usage (source: Spring #Autowire on Properties vs Constructor)
I also personally prefer to have my own #Bean methods as much as possible because it gives me much more control.
So I have this class (used lombok):
#RequiredArgsConstructor
#Service
public class ServiceImpl {
private final BookService bookService;
#Autowired
private TableService tableService;
public void method() {
if (!bookService.isEmpty()) return;
Object table = tableService.getObject();
}
}
My test class:
#ExtendWith(MockitoExtension.class)
#RunWith(JUnitPlatform.class)
public class ServiceImplTest {
#Mock BookService bookService;
#Mock TableService tableService;
#InjectMocks ServiceImpl serviceImpl;
#Test
public void testMethod() {
when(bookService.isEmpty()).thenReturn(true);
when(tableService.isEmpty()).thenReturn(new Object());
serviceImpl.method();
}
}
If I run it, the bookService is null. It is not injected in the class. When the code is at the if (!bookService.isEmpty()) return; the bookService is null.
if I change my testMethod to this:
#Test
public void testMethod() {
MockitoAnnotations.initMocks(this);
when(bookService.isEmpty()).thenReturn(true);
when(tableService.isEmpty()).thenReturn(new Object());
serviceImpl.method();
}
So I added MockitoAnnotations.initMocks(this); then the code Object table = tableService.getObject(); in my class returns null. So the tableService is not null, but the getObject() return null;
Any idea how to fix it? I am not let to change the class code.
Thanks in advance.
I have a spring boot test as below
#SpringBootTest(class=AppConfig.class)
Public class AppTest{
#Autowired
private Product product
#Test
Public void test(){
.....
.....
}
}
My AppConfig.class is as below
Public clas AppConfig{
#Mock
EMailService emailService;
public AppConfig(){
MockitoAnnotations.initMocks(this)
}
#Bean
Public Product getProduct(){
return new Product();
}
}
Class Product{
#Autowired
private EMailService emailService
.....
......
}
Even after i defined #Mock EMailService emailService, whem i run the test, I get error EMailService bean not defined.
In your AppTest class
#SpringBootTest(class=AppConfig.class)
public class AppTest{
#Mock
private EMailService emailService;
#InjectMocks
private Product product;
#Test
public void test(){
.....
.....
}
}
Also, I think you do not need the definitions in the AppConfig class anymore
Is it possible to just ignore/mock any injected dependencies inside a MockedBean?
Example:
#Service
public class MyService {
#Autowired
private MailerService mailer;
public void test1() {
//does not use mailer
}
public void test2() {
//...
mailer.send();
}
}
#Service
public class MailerService {
//I want these to be automatically mocked without explicit declaration
#Autowired
private JavaMailSender sender;
#Autowired
private SomeMoreService more;
//also these should be mocked without having to provide properties
#Value("${host}") private String host;
#Value("${user}") private String user;
#Value("${pass}") private String pass;
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MyServiceTest {
#Autowird
private MyService myservice;
#MockBean
private MailserService mailer;
#Test
public void test1() {
myservice.test1();
}
}
I could use #MockBean to sort out mailer injection dependency. But any service inside the mocked bean would also have to be explicitly mocked.
Question: is it possible to mock a service "away". Means, just mock the bean and don't care what's inside the #MockedBean (or automatically also mock anything inside #MockedBean)?
As for me the best way to inject mocks is to use MockitoJUnitRunner
#RunWith(MockitoJUnitRunner.class)
public class MocksTests {
#InjectMocks
private ParentService parent;
#Mock
private InnerService inner; // this will be injected into parent
//your tests
}