I am developing a REST API with Spring Boot.The problem it's that I have one interface and two implementations and I want to test only with the mock implementation.
Interface CRMService
#Service
CRMServiceImpl
#Service
CRMServiceMock
Implementations: the first one is the real integration with the backend and the second is a mock for testing purposes, what's the best approach? Integration test or test based on the active profile ? If I need to autowire a service based on profile what's the best practice?
While I'm sure there's exceptions, generally it shouldn't be integration or unit tests (often involves mocks), but both; see testing pyramid concept.
Integration tests: just use the real service. If it calls out to other live services, then consider injecting the URLs as Spring Boot properties which point to mock servers in the test environment (Node.js or something easy and quick).
Unit tests: Consider using a test-framework like Mockito. Using this you can write your tests with mocks approximately like so:
private CRMServiceImpl mockService = mock(CRMServiceImpl.class);
#Test
public void someTest() {
when(mockService.someMethod(any(String.class), eq(5))).thenReturn("Hello from mock object.")
}
The above example roughly translates to "when some class invokes 'someMethod(String, int)' on your service, return the String specified".
This way allows you to still use mocks where necessary, but avoids having to maintain entire mock implementation profiles and avoids the problem of what to auto-wire.
Finally, if you need a full separate implementation, consider not auto-wiring services! Instead, use #Bean annotations in your configuration class and inject it via constructors into the classes that need it. Something like so:
#Configuration
public class ApplicationConfiguration {
#Value{$"service.crm.inmem"} // Injected property
private boolean inMem;
#Bean
CRMService getCRMService() {
if (inMem) {
return new CRMServiceMock();
}
return new CRMServiceImpl();
}
#Bean
OtherService getOtherService() {
// Inject CRMService interface into constructor instead of auto-wiring in OtherService.class
return new OtherService(getCRMService());
}
}
An example of when you could use ^^ would be if you wanted to switch between an in-memory store, and a real database-connection layer.
Personally I'd suggest doing dependency injection like the above example even when there aren't multiple implementations since as a project grows, if an auto-wired property fails it can be difficult to track down exactly why. Additionally explicitly showing where dependencies come from can help with organizing your application and visualizing your application hierarchy.
Related
I'm developing a MessageHandler class which contains a Cache field which is also I developed, MessageHandler is annotated as a #Component and Cache is not bean.
When using Spring-boot-test to test the MessageHandler class, I use #Mockbean to mock a MessageHandler and try to use setter to set a Cache object for the mock bean. But when I run test to see if MessageHandler has a cache, I got null.
My code is as follows:
#MockBean(value = HiMessageHandler.class)
MessageHandler messageHandler;
public void setUp(){
Cache cache=new Cache();
cache.offer(new HiMessage("1","1","1"));
messageHandler.setCache(cache);
}
#Test
public void testConsume() throws Exception {
setUp();
System.out.println(messageHandler.getCache());
}
I want to know how to set a field that is not a bean for a mock bean.
I think the main issue here is that you try to mock the class that you test.
You test MessageHandler class in an integration test (otherwise, if its a unit test - then spring boot test is not required)
So the goal is to check that the code that you wrote in MessageHandler really works, and since its an integration test, probable with an emphasis on interaction with other real/semi-real components.
Side note, if you're not sure about the difference between unit and integration tests feel free to read my answer on this question or just google, it will provide more valuable resources for sure, the point is you should clearly understand the difference between the two and pick the right tool for the job.
On the other hand, when you use #MockBean annotation, Spring creates a Mock for your bean, and puts this mock instead of the real bean into application context, so that other beans, if dependent on this message handler, could get the reference on this mock instead of the reference on the real bean.
Then you call some method on a mock (setCache) and indeed it doesn't work, because mock is not a real thing, its a mock, the correct way to interact with mock is to specify interaction rules (style when(...).thenReturn(...) in mockito syntax). Yes, it doesn't work, so this is a direct answer on your question, but as I've explained above the issue is deeper here IMHO.
Let's say you have a controller like so and you want to change the autowired dependency. What are ways to have one implementation for your project under /src/main/java and then have a mock dependency in your test code under /src/test/java. The code under /src/main/java will get built into the web application.
#RestController
#RequestMapping("/u/util/")
public class HealthCheckAdapter {
#Autowired
private HealthDbService healthCheck;
}
The HealthDbService is a Java interface, and here an example concrete class. I could change the qualifier below, but Spring will still pick this up in the test cases?
#Repository
#Qualifier("basicHealthCheckService")
public class HealthCheckDefaultManager implements HealthDbService {
}
Simplest, recommended way: use constructor injection:
#RestController
#RequestMapping("/u/util/")
public class HealthCheckAdapter {
private HealthDbService healthCheck;
#Autowired // not even needed in recent versions
public HealthCheckAdapter(HealthDbService healthCheck) {
this.healthCheck = healthCheck;
}
}
And now in your unit test:
new HealthCheckAdapter(new FakeHealthDbService());
or, using a mocking API like Mockito:
HealthDbService mockHealthDbService = mock(HealthDbService.class);
new HealthCheckAdapter(mockHealthDbService);
If you use Mockito, it can also inject mock dependencies using annotations, even if you keep using field injection.
And finally, if you want to use mock MVC tests (to test the annotations, the JSON serialization, etc.), you can use the Spring testing support (which is even easier to use in Spring Boot)
I'm doing some integration tests, on a Spring Boot application.
Usually the integration tests that I was used to develop, was regarding the application domain, without any external service involved.
Since this time I need to make an integration test on a service which uses both a database and an external service called by an SDK, I've tried doing something like the following:
#RunWith(PowerMockRunner::class)
#SpringBootTest
#PowerMockRunnerDelegate(SpringRunner::class)
#PrepareForTest(McpProductService::class)
class MyServiceIntegration {
#Mock
private ExternalService externalService;
#Autowired
#InjectMocks
private MyServiceImpl myService;
#Test
public void thisTestShouldWork() {
...
}
}
What is confusing me is: how should I declare myService attribute? Usually when I use Mockito + PowerMock in my Unit Tests, I usually test the implementation, not the whole Service Interface + Spring Injection. But I can't use #Autowired if I'm using just it's implementation, not the Interface.
Is there any best practice for this issue that I'm facing?
Disclaimer: I'm assuming that what you are after is an end-to-end test of a service interface, backed by multiple classes. I assume (and hope) that you don't have a single class handling both database and webservice integration.
I don't see the need to use PowerMock here, it is usually something one would use for testing legacy code with a lot of static stuff. If you are using Spring boot, your code should be of a quality that makes PowerMock unnecessary.
When writing an end-to-end test, the principles are the same as a per-class unit test, only with a larger scope:
With a unit test, you create an instance of the class under test, and mock all its external dependencies (other classes)
With an end-to-end test, you create an "instance" of your module under test, and mock its external dependencies.
So, here you should find a mechanism to mock the parts of your code that communicates with external sources, like web service clients, database classes (if you don't use an in-memory db for your test (you should)). This will typically be a Spring config that is almost identical to the one used in production, but with said parts mocked out. Then, you just #Inject the parts you need to communicate with in order to complete the test.
Assuming that you use component scan and annotations for all beans, you could mock the endpoint-classes and use profiles:
This code is based on memory only, might not work on copy-paste, but hopefully you could use the concepts..
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
#Primary
public SomeWebserviceClient someWebserviceClient() {
return mock(SomeWebserviceClient.class);
}
}
Production code:
#Service
public class SomeClass {
#Inject
private SomeWebserviceClient client;
}
Then in the test:
#RunWith(PowerMockRunner::class)
#SpringBootTest
#ActiveProfiles("test")
public class SomeTest {
#Inject
private SomeClass someClass;
#Inject
private SomeWebserviceClient client; //<< will inject mock
}
Mock will also be injected into SomeClass
I'm trying to unit test a class; for the sake of brevity we'll call it Apple. It has a #Required setter that takes an AppleManager bean. AppleManager itself has a #Required setter than takes an AppleStorageClient bean. I am mocking AppleManager and injecting it into Apple, but I also need to use the real AppleManager class to retrieve data using methods from its AppleStorageClient object. How can I achieve this with Spring/Mockito?
Test:
public class AppleTest {
#InjectMocks
private Apple apple;
#Mock
private AppleManager appleManager;
?????
private AppleManager realAppleManager;
//I tried = new AppleManager() here but the object is null...
//ostensibly because Spring doesn't know I want to use the bean
//also tried #Autowired to no avail
#Before
public void doBeforeStuff() {
MockitoAnnotations.initMocks(this);
}
...
}
Source:
public class Apple {
private AppleManager appleManager;
#Required
public void setAppleManager(AppleManager appleManager) {
this.appleManager = appleManager;
}
....
}
&
public class AppleManager {
private AppleStorageClient appleStorageClient;
#Required
public void setAppleStorageClient() {
this.appleStorageClient = appleStorageClient;
}
...
}
In general it looks like something is 'uncomplete' here. I'll explain why.
Technically If you're using spring - it doesn't sound like a unit test to me anymore, probably integration test or something.
Unit tests are in general should be really-really fast and starting up spring won't let them pass fast enough (think about having thousands of unit tests in your project each of them running spring on startup - it will take them ages to complete).
But let's say its only about definitions. When you're using spring testing framework with JUnit, someone has to start and maintain a spring context to do all the Dependency Injection magic and apply it to the test case.
In Junit implementation a special Runner (a JUnit abstraction) is required:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:my-test-context.xml" }) // or use Java Config
This doesn't appear in the question though.
So now Spring will create a context and will attempt to inject beans. And we have effectively reduced our issue to the issue of having two implementations of the interface and asking spring to inject implementation by interface so that two different implementations will be injected. There are 2 solutions I can see here:
Create a Mock outside spring - you probably won't specify your expectations in spring anyway. Maintain only a "real apple manager" in spring
Maintain both in spring but in your test case use a #Qualifier annotation
Now what I would like to emphasize is that if you maintain real apple manager that contacts "apple store" (probably a database, with driver support, transaction management and so forth) you'll have to create a test context so that it will be able to connect to that database, and if the apple manager internally injects its dependencies via spring, then these beans are also have to be specified.
So that if in future you'll change something in the underlying store (say, add a dependency in a driver to another spring bean, this test context will automatically become broken). Just be aware of this and inject beans wisely.
I am pretty new to Spring and I study using "Spring in Action" (fourth edition) by Craig Walls. The interest is not only on how to write code that is working, but also on the correct principles of using Spring.
Regarding the following piece of code from page 142, Listing 5.6:
public class HomeControllerTest {
#Test
public void testHomePage() throws Exception {
HomeController controller = new HomeController();
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/")).andExpect(view().name("home"));
}
}
My questions are generated by the following thoughts:
The general understanding is that Spring uses Dependency Injection as a way to reduce the management overhead of object dependencies, increase modularity, simplify testing and code reuse. However, doesn't it imply that beans must be created and managed by the container? Since I started reading on the subject, the first detail that I memorized stated that new should never appear in a well-written piece of code that follows DI.
Could this be a solution in case we want to test a Stateful bean? I mean, if there are multiple independent tests to be run on the same instance, each of them testing the same state of the bean. Even though I found out that there is a suitable annotation for doing this (#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)).
Is there another use case that is difficult or impossible to solve otherwise (except for using new)?
A more 'to the letter' implementation would use #ContextConfiguration to specify the ApplicationContext.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = WebConfig.class)
#WebAppConfiguration
public class HomeControllerTest {
#Autowired
HomeController controller;
#Test
public void testHomePage() throws Exception {
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/")).andExpect(view().name("home"));
}
}
yes, you shouldn't use new to create Spring bean instances (otherwise they're not Spring beans anymore) in production code. But the whole point of DI is to let you create and manually inject your objects with fake dependencies in unit tests. So the test code here is perfectly fine.
Yes, each unit test is free to create its own bean instance and to populate it the way it needs to. Stateful beans are extremely rare, though. They're usually stateless.
Another place where using new to create a Spring bean is precisely in #Bean-annotated methods of configuration classes. The whole point of these methods is precisely to create and initialize the Spring beans that will then be used and injected by Spring. But again, using new in unit tests is absolutely fine, and the right thing to do.