Should #Autowired and #SpringBootTest be used in unit tests? - java

In a project where I work, we have been initializing Services for Unit testing in the following way:
Mock dependencies that are needed for the Service.
Create the Service using a constructor.
Something like this :
#RunWith(SpringRunner.class)
public class ServiceTest extends AbstractUnitTest {
#Mock private Repository repository;
private Service service;
#Before
public void init() {
service = new Service(repository);
when(repository.findById(any(Long.class))).thenReturn(Optional.of(new Entity()));
}
}
But our new developer proposed to use #Autowired and #SpringBootTest
#SpringBootTest(classes = ServiceTest.class)
#MockBean(classes = Repository.class)
#RunWith(SpringRunner.class)
public class ServiceTest extends AbstractUnitTest {
#MockBean private Repository repository;
#Autowired private Service service;
#Before
public void init() {
when(repository.findById(any(Long.class))).thenReturn(Optional.of(new Entity()));
}
}
Before that, I supposed that #Autowired and #SpringBootTest should be used only in integration tests. But googled a lot and I see that some people use those two in Unit tests.
I read boot-features-testing. Also, I read this Unit tests vs integration tests with Spring.
For me, it still doesn`t feel well that we need to involve Spring to do dependency injection for unit tests, as we can do this by ourselves to do the unit testing.
So, should #Autowired and #SpringBootTest be used in unit tests?

No. A unit test is to test a single component in isolation. Using constructor injection in your beans allows you to very simply call new SomeService(myMock), no Spring required.
Writing component or functional tests (testing your application but not wiring it up to external services for a full integration test, mocking only external interfaces; this is good for things like MockMvc tests) is a good match for #SpringBootTest, and in that case you may need to create mock objects in a Spring configuration and autowire them into your test so you can manipulate them.

In TDD tests should be helpful, straight forward, fast and keep maintenance at minimum. Otherwise devs will be annoyed and try to avoid tests.
So I recommend not to be to strict if it's a pure unit-test or a bit of a integration. Choose the test-scope that suits best for your situation and use the technical features that fit into this scope.
Don't use DI if you are doing a "real" unittest, testing a self-contained method on its own. Those tests make sense if the method does something meaningful, e.g. algorithm, calculation, decision making. Mocking sources of data is great here to get predictible input values.
The downside of a #SpringBootTest that you don't really need is a hell of a startup-time (depends on project size) which is really annoying.
Use CDI if a method calls functionality on a dependency. 1) Setting myService.service2 = new Service2() by hand leaves you with a Service2 that is also not handled by a DI-Container which maybe requires you to set some more dependencies... 2) CDI in testing is a breeze with Spring - so why would you bloat your tests with setup-code? 3) DI involves proxies which sometimes behave a little different from simple instances.
Use #ContextConfiguration(classes = {ServiceTest.class}) to get CDI with a faster startup compared to #SpringBootTest.
Don't test glueing code with unittests as it does not have any intrinsic value. Those tests are hard to understand (who loves documenting a test?), will require a lot of mocking and will often be subject to change. Test such code in association with other methods/classes even if it means you only have integration tests for these parts of the code.

Related

How to inject mock for only one test case with Quarkus/RestAssured

I'm attempting to test a REST controller (using Quarkus) endpoint using rest assured. I want to mock a class that is injected into that controller (ideally with Mockio), but only for one of my tests. Or get different behaviour per test case without having to have separate classes for each test. I'm not sure how to do this?
I've seen doing it the way from the documentation:
#Mock
#ApplicationScoped
public class MockExternalService extends ExternalService {
#Override
public String service() {
return "mock";
}
}
But this would only allow me to use one mock for all tests and not mock certain behaviours based on tests as I would with Mockito. I think?
I've tried creating a mock and annotating it with #Mock
#Mock
public TableExtractorService tableExtractorServiceMock = Mockito.mock(TableExtractorService.class);;
but I still get my real implementation when I use it. I'm using a constructor annotated with #Inject in my Controller that takes the TableExtractorService.
For a bit more information my test using restassured looks like this:
InputPart filePart = Mockito.mock(InputPart.class);
Mockito.when(tableExtractorServiceMock.Extract(anyObject()))
.thenThrow(IOException.class);
final InputStream inputStream = filePart.getBody(InputStream.class, null);
given()
.multiPart("file", inputStream)
.when().post("/document")
.then()
.statusCode(500);
That endpoint calls the service class that I'm trying to mock, and I want that mock to return an exception.
It can't be done. Quarkus documentation explains the issue:-
Although this mechanism is fairly straightforward to use, it nonetheless suffers from a few problems:
A new class (or a new CDI producer method) needs to be used for each bean type that requires a mock. In a large application where a lot of mocks are needed, the amount of boilerplate code increases unacceptably.
There is no way for a mock to be used for certain tests only. This is due to the fact that beans that are annotated with #Mock are normal CDI beans (and are therefore used throughout the application). Depending on what needs to be tested, this can be very problematic.
There is a no out of the box integration with Mockito, which is the de-facto standard for mocking in Java applications. Users can certainly use Mockito (most commonly by using a CDI producer method), but there is boilerplate code involved.
Link for reference: https://quarkus.io/blog/mocking/
According Quarkus test documentation, you can do it usingo #QuarkusMock or #InjectMock.
As #Ankush said, a class annotated with the #Mock annotation is using the CDI #Alternative mechanism, and will be global. #QuarkusTestProfiles can be used to define CDI #Alternatives for groups of tests.
For example, instead of annotating the mock with #Mock, it could be referenced in a test profile as
default Set<Class<?>> getEnabledAlternatives() {
return Set.of(MyMockThing.class);
}
Any test annotatated with the
#TestProfile(MyMockyTestProfile.class)
profile would get those mocks, while others would use the original implementation.
What may be a simpler method is to just use #InjectMock. For example, in the test class, declaring a field like this:
#InjectMock
MyThing mock;
will ensure that mock is used by the classes under test, just for this test.
For rest clients, it will also be necessary to add a #RestClient annotation, and if the original implementation is a singleton, convertscopes can be used to coax the scopes into something mockable.
#RestClient
#InjectMock(convertScopes = true)
MyThing mock;
Behaviour can be added to the injected mock in #BeforeEach or #BeforeAll methods. For example
#BeforeEach
public void setup() {
when(mock.someMethod()).thenReturn("some value");
}

Integration testing without Spring context

Should I always start Spring context during integration tests? (I mean using #SpringBootTest annotation)
Currently I'm writing integration test that involves a few classes and in order to make it faster I create object graph by hand (i.e. I don't start Spring IoC container). So currently my integration test (written in Spock) looks like this:
class UserConverterIT extends Specification {
UserConverter converter = new UserConverter(new UserDtoFactory(new UserGroupPolicy()))
def 'should ...'() {
when:
converter.convert(...)
then:
...
}
}
Alternatively I could add #SpringBootTest annotation, put #Autowire above UserConverter field and all dependecies would be injected automatically. However, the first approach is much faster. Is there something wrong in this approach?
As you said the #Autowired annotation would inject all dependencies and load the whole context automatically. Your approach is also working but its really fragile!
How can you guarantee that in your tests you never need some beans which you didn't manually new them?
Also there is another important thing. When you let the spring to inject the dependencies, if there is a problem on declaring the beans, problem would show on test phase, but in your approach they won't identify.
Also you might sometimes #Autowired an interface which told spring to get the implementation on runtime. For example you have a parent module which has an interface that implements in the child module. When you want to write a test case in parent you don't have access to child implementation to make new of that.

How to write integration/system test with Spring Boot to test service with repository

I have #Service in Spring Boot app which is parsing file and then storing it into DB using Spring Data JPA. I want to test this parsing with all logic of changing this mapping. So, for this I need to store mapping in DB in test.
#Service
public class ParsingService {
#Autowired
private StoringInDBRepository storingInDBRepository;
}
#Repository
public interface StoringInDBRepository extends JpaRepository<MyEntity, String> {
The problem is that during test with annotation #SpringBootTest I don't want to load the whole context, but only my ParsingService.
When I writing :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ParsingService.class})
public class ParsingServiceTest{
#Test
public void someTest(){
}
}
This test couldn't be initialized because I am not loading StoringInDBRepository in #SpringBootTest annotation. However I can do it, because StoringInDBRepository is an interface. Usage of #DataJpaTest according to javadoc will be correct only if I'm testing repository layer. Usage of #DataJpaTest and #SpringBootTest is not recommended.
How should I test such services?
Thanks in advance.
So, after all investigations I found a couple solutions. I chose to enable repositories and service with
#SpringBootTest(classes = {ParsingService.class})
#EnableJpaRepositories(basePackages = "com.my.repository")
#EntityScan("com.my.entity")
#AutoConfigureDataJpa
This is a workaround. And I don't think it is the best. The other solution is to create a #TestConfiguration which will return your service and all to use #DataJpaTest annotation to enable repository classes. In this case you should use #SpringBootTest annotation
You could use a #SpyBean to get what you want. It is fairly easy to use.
However - You don't actually need to test it in that way. Because by having your application context tested, you are making sure all classes get injected/autowired as they should be. Then separately test the method on the service level with mocks. And lastly, use #DataJpaTest to test it in the repository/DB level.
So you decouple your tests nicely: integration tests / unit tests / repository tests
There is no need to tightly couple all those three things in one class or test method.

Does Spring provide stub implementation of JpaRepositories?

I am trying to unit test my Service classes that looks similiar to this:
#Service
public class SomeQueryService {
private final SomeRepository repository;
public SomeQueryService(SomeRepository repository) {
this.repository = repository;
}
public void doSomething() {
// code doing some magic
}
}
SomeRepository is simple repository interface extending JpaRepository interface.
What I want to do is unit test this service to verify whether it is working properly.
I do not want to use mockito to mock repository behaviour instead, I want to have some in-memory implementation (on list or map) that will imitate database behaviour.
Does Spring provide such fake implementations?
I want to avoid making Stub Implementation of such repository by myself as I will be using such tests in many other places.
RealLifeDeveloper has created an MIT-licensed helper-class to do just what you want: implement the repository-interface with a plain-old-java-object that just wraps a Collection, and called it "InMemoryJpaRepository". You will still have to implement some logic yourself1, though it should be easy if your queries are not too complicated.
An article explaining how to do this with example: https://reallifedeveloper.com/creating-in-memory-versions-of-spring-data-jpa-repositories-for-testing/
The repository (which includes other stuff, too) is on github: https://github.com/reallifedeveloper/rld-build-tool
The specific helper-files for creating the inmemory-db are found at https://github.com/reallifedeveloper/rld-build-tools/tree/master/src/main/java/com/reallifedeveloper/tools/test/database/inmemory if you dont want the whole repo.
1 The rule "Don't put logic in tests" exists for a reason and is clearly violated here. However, the well-known and widely-used alternatives mentioned by the other answers, H2-testing and extensive mocking, have their drawbacks too.
The type of testing you are referring to is called "Integration Testing" or "End to end testing" since it tests the whole application or a big chunk of it compared to unit tests that test only one method.
https://www.guru99.com/unit-test-vs-integration-test.html
You should not unit test your repositories, since they are already well tested by the spring team.
Solution:
You can create a test that starts the whole spring container using Spring Boot:
Just create a class in your test folder and annotate it with:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTestClass {
#Test
public void test() {
}
}
You can then configure an embedded database using H2 so that your test does not use the production database, just follow the Spring Boot Database Initialization doc.
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html
PS. You can also create a specific profile using the following annotation on your test class:
#ActiveProfiles("test")
No Spring Data does not provide a fake implementation of that interface.
You'll have to create it on your own or use a mocking framework like Mockito.

how to unit test method dependent on springBoot applicationContext?

I am trying to write a unit test for a static method which takes a class and method name and does some reflection to call the method with arguments and store the results. I'm using spring-boot.
My test actually works when I run the full suite, but when I run the test as standalone it fails. The problem is that I've created a mock class (a hand written mock, not using mockito or easymock) which I want the static method to use. However, the reflection can not detect my mock class because the class has not been loaded into the applicationContext by spring-boot. Here is the line that fails:
T proxy = SpringApplicationContext.getBean(clazz);
SpringApplicationContext definition:
#Component
public class SpringApplicationContext implements ApplicationContextAware
{
private static ApplicationContext applicationContext_;
#Override
public void setApplicationContext(ApplicationContext applicaitonContext) throws BeansException {
applicationContext_=applicaitonContext;
}
public static <T> T getBean(Class<T> requiredType) throws beanException {
return applicationContext_.getBean(requiredType);
}
*note, I had to retype by hand, please assume obvious syntax errors are typos.
so basically my applicationContext is not being set or defined. I only need one mock bean in the applicationContext, I could do it by hand, but is there a more spring approach using annotations?
It turns out that my test didn't work rather the were run stand alone or part of a suite, I had a separate issue with using the wrong annotations for #BeforeTest which masked the defect when running the whole suite.
The fix was pretty simple. I added the SpringApplicationConfiguration annotation above my test:
#SpringApplicationConfiguration(classes =
{
MockController.class,
SpringApplicationContext.class
}
public class MyTest extends AstractTestNGSpringContextTests
There are two parts to this. The #SpringApplicationCOnfiguration loads only those values I listed. I could have pointed to configuration classes, but that would ultimately load most of the beans in my enviroment which is overkill for a unit test. So I load the two #component objects needed in my ApplicationContext for my unit test to work only.
I also had to extend AbstractTestNGSpringContextTests because It's the only way to get spring to play nice with the TestNG kit were using for our tests. If others are using junit tests instead of TestNG don't extend the AbstracTestNGSpringContextTests, instead I believe your want to add the annotation:
#RunWith(SpringJUnit4ClassRunner.class)
Though I haven't used it since I'm not using junit.
Hopefully this answer saves others who are trying to figure out how to load only a few classes instead of the entire enviroment (most examples I found want you to load configuration files that will load every bean, which is slow and honestly undesirable in a unit test).
Arguably I should still have mocked out the SpringApplicationContext entirely, I'm lazy and sloppy :)

Categories

Resources