This is my first time carrying out junit testing so forgive me if this is a stupid question. The class from my Spring web application which I wish to test is below. The class extends WebMcvConfigurerAdapter to add view controllers.
I just want to test if each of the view controllers maps to the correct view. In every tutorial I've looked at, the test is carried out for a controller which has it's own separate class. it wouldn't make sense for the controllers below to have their own class as there is no logic involved in them. Can anyone direct me for the way i should approach this or give sample code? Do controllers like these which only link to a view even require testing?
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("greeting");
registry.addViewController("/portal").setViewName("portal");
registry.addViewController("/login").setViewName("login");
}
}
It depends on the type of testing you are implementing. For unit testing its not necessary since you will be testing Spring internal workings. However for integration testing its necessary so that you can check if your application is wired correctly.
An example of an integration test for your view controllers could be
#RunWith(SpringJunitClassRunner.class)
#ContextConfiguration(classes = MvcConfig.class)
public class ViewControllerIT{
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testLogin() throws Exception {
this.mockMvc.perform(get("/login")
.andExpect(status().isOk())
.andExpect(view().name("login"));
}
}
Related
I'd like to test my Spring Boot command line application. I would like to mock certain beans (which I was able to do by annotating #ContextConfiguration(classes = TestConfig.class) at the top of my test class. In TestConfig.class, I override the beans that I would like to mock. I'd like Spring Boot to find the rest of the components. This seems to work.
The problem is that when I run the test, the entire application starts up as normal (ie. the run() method is called).
#Component
public class MyRunner implements CommandLineRunner {
//fields
#Autowired
public MyRunner(Bean1 bean1, Bean2 bean2) {
// constructor code
}
#Override
public void run(String... args) throws Exception {
// run method implementation
}
I've tried to override the MyRunner #Bean and put it in TestConfig.class, but that doesn't seem to work. I understand that I'm loading the regular application context, but that's what I'd like to do (I think?) since I would like to re-use all (or most) of the #Component I created in my Application, and only mock a tiny subset.
Any suggestions?
EDIT:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The answer was simpler than I thought. Add the MockBean in
#TestConfiguration
public class TestConfig {
#MockBean
private MyRunner myRunner;
}
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
So MyRunner.run() is never called but I can still use all the other beans in my application.
CommandLineRunners are ordinary beans with one exception:
After the application context is loaded, spring boot finds among all its beans the beans that implement this interface and calls their run method automatically.
Now, I would like you to ask to do the following:
Remove ContextConfiguration from the test and place a breakpoint in constructor of MyRunner. The test should look like this:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#Autowired
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test and make sure that myRunner is loaded and its run method is called
Now mock this class with MockBean annotation:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#MockBean
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test. Make sure that run method is not running. Your Application Context now should contain a mock implementation of your component.
If the above works, then the problem is with TestConfig and ContextConfiguration annotation. In general when you run without ContextConfiguration you give spring boot test engine a freedom to mimic the application context started as if its a real application (with autoconfigurations, property resolution, recursive bean scanning and so forth).
However if you put ContextConfiguration, spring boot test doesn't work like this - instead it only loads the beans that you've specified in that configuration. No Autoconfigurations, no recursive bean scanning happens for example.
Update
Based on OP's comment:
It looks like the MyRunner gets loaded when you put #ContextConfiguration because of component scanning. Since you have an annotation #Component placed on MyRunner class it can be discovered by Spring boot engine.
In fact there is a "dangerous" mix of two types of beans definitions here:
1. The beans defined with #Bean in #Configuration annotation
2. The beans found during component scanning.
Here is the question for you: If you don't want to mimic the application startup process and instead prefer to load only specific beans, why do you use #SpringBootTest at all? Maybe you can achieve the goal with:
#RunWith(SpringRunner.class)
#ContextConfiguration(YourConfig.class)
public class MyTest {
...
}
One way you could do this is to have 2 classes with the main method, one which sets up the "normal" context, and another that sets up the "mock" context:
Normal App Context, uses the usual Application
#SpringBootApplication(scanBasePackages = "com.example.demo.api")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am not mocked");
}
#Bean
public Bar bar() {
return new Bar("this is never mocked");
}
}
Add another Application class that overrides the normal context with the mocked one
#SpringBootApplication(scanBasePackageClasses = {MockApplication.class, Application.class})
#Component
public class MockApplication {
public static void main(String[] args) {
SpringApplication.run(MockApplication.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am mocked");
}
}
When you run Application.main Foo will be "I am not mocked", when you run MockApplication.main() it will be "I am mocked"
I have a RepositoryRestController that exposes resources for some persistent entities.
I have a method on my controller that takes a PersistentEntityResourceAssembler to help me generate the resources automatically.
#RepositoryRestController
#ExposesResourceFor(Customer.class)
#RequestMapping("/api/customers")
public class CustomerController {
#Autowired
private CustomerService service;
#RequestMapping(method = GET, value="current")
public ResponseEntity getCurrent(Principal principal Long id, PersistentEntityResourceAssembler assembler) {
return ResponseEntity.ok(assembler.toResource(service.getForPrincipal(principal)));
}
}
(Contrived example, but it saves going into too much detail about irrelevant details of my use-case)
I'd like to write a test for my controller (my real use-case is actually worth testing), and am planning on making use of #WebMvcTest.
So I have the following test class:
#RunWith(SpringRunner.class)
#WebMvcTest(CustomerController.class)
#AutoConfigureMockMvc(secure=false)
public class CustomerControllerTest {
#Autowired
private MockMvc client;
#MockBean
private CustomerService service;
#Test
public void testSomething() {
// test stuff in here
}
#Configuration
#Import(CustomerController.class)
static class Config {
}
}
But I get an exception saying java.lang.NoSuchMethodException: org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.<init>()
Presumably something is not being configured correctly here because I'm missing the entire data layer. Is there some way of mocking out the PersistentEntityResourceAssembler? Or another approach I could use here?
I ended up for now with:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
The downsite of it is that the test would start the full Spring application context (but without the server).
I ended up doing a slightly hacky solution here:
I removed PersistentEntityResourceAssembler from the controller method.
I added an #Autowired RepositoryEntityLinks to the controller, on which I call linkToSingleResource to create the links as needed.
I added an #MockBean RepositoryEntityLinks to my test class, and configured the mocking to return something sensible:
given(repositoryEntityLinks.linkToSingleResource(any(Identifiable.class)))
.willAnswer(invocation -> {
final Identifiable identifiable = (Identifiable) invocation.getArguments()[0];
return new Link("/data/entity/" + identifiable.getId().toString());
});
It's far from ideal - I'd love to know if there's a way of getting just enough of the data layer up that I can depend on PersistentEntityResourceAssembler.
I would like to be able to use a bean via auto-wiring and without having to directly use an ApplicationContext. Below is a dummy example of what I would like to be able to do.
Configuration Class
#Configuration
public class CoffeeConfig
{
#Bean
public CoffeeMachine provideCoffeeMachine()
{
return new CoffeeMachine(provideCoffeeBean());
}
#Bean
public CoffeeBean provideCoffeeBean()
{
return new CoffeeBean(Type.BEST);
}
}
Coffee Shop Class
#Component
public class CoffeeShop
{
#Autowired
private CoffeeMachine cMachine;
public void pourCoffee()
{
System.out.print("Pouring cup of coffee: " + cMachine.pour(Amount.8OZ));
}
}
In order to solve this, I have been reading through spring documentation and spring tutorials. The problem is, I haven't seen anyone attempt to illustrate how to do something as simple as this, and when they do, they end up resorting to using an application context. That being said, I know that if I am running unit tests with Spring, I can do the following:
Test Class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=CoffeeConfig.class, loader=AnnotationConfigContextLoader.class)
public class SpringIOCTests
{
#Autowired
public CoffeeMachine cMachine;
#Test
public void influxDevTest()
{
assertEquals(Type.BEST, cMachine.getBeans());
}
}
The way this configures leads me to believe that using auto-wiring in such a way should be attainable in the actual application instead of using these test-only dependencies such as the ContextConfiguration. I should also note that this unit test does pass.
Does Spring offer a methodology in which one can auto-wire dependencies in a nice and clean way avoiding the direct use of an application contexts?
We have many Controllers in our system, and many Spring Data repositories.
I would like to write tests for my controllers that run through my MVC context.
However, it seems pretty cumbersome, and just not right, to have to, by hand, mock every service and repository in my system, so that I can test the controllers
e.g.
FooControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy(value = {
#ContextConfiguration(classes = { MockServices.class }),
#ContextConfiguration({ "classpath:/META-INF/spring/mvc-servlet-context.xml" }),
})
public class FooControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mvc;
#Autowired
private FooRepository fooRepository;
#Autowired
private FooService fooService;
#Before
public void setUp() throws Exception {
mvc = webAppContextSetup(wac).build();
}
#Test
public final void list() {
when(fooRepository.findAll()).thenReturn(...);
mvc.perform(get("/foo"))...
}
#Test
public final void create() {
Foo fixture = ...
when(fooService.create(fixture)).thenReturn(...);
mvc.perform(post("/foo"))...
}
}
MockServices.java
#Configuration
public class MockServices {
#Bean
public FooRespository fooRepositiory() {
return Mockito.mock(FooRespository.class);
}
#Bean
public FooService fooService() {
return Mockito.mock(FooService.class);
}
//even though we are "only" testing FooController, we still need to mock BarController's dependencies, because BarController is loaded by the web app context.
#Bean
public BarService barService() {
return Mockito.mock(FooService.class);
}
//many more "mocks"
}
I do not really want to use standaloneSetup() (want to use the production configuration, eg conversion services, error handlers, etc)
is this just the price I have to pay for writing controller tests so far down the line?
Seems there should be something like mock every class annotated with #Service or mock every interface that extends JpaRepository
An MVC Controller is implemented normally like a glue code that integrates the Model with the View. For example, when invoking an EJB from the Controller and then updating the View model.
So, a Controller test may have sence when indeed you mock all your dependencies and verify that this integration or "glue code" is working as expected. In general, if an integration test implies too many components, maybe a modularization of the whole sut may be necessary for the system to be actually testable.
Anyway, if you find integration test laborious, maybe you can try to get the most coverage for each standalone component and let functional tests get the Controller coverage.
I have a class annotated with #WebListener, extending ServletContextListener that another class I'm testing relies on having executed. When I test manually (i.e. running in Tomcat) it works, but within my JUnit test the #WebListener class is never executed. I'm assuming that I need to add something to my configuration to get it to execute, but I'm not sure what. Or do I need to run it manually in the the test?
EDIT:
Here's the basics of the class
#WebListener
public class MyListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent event) {
// Retrieve spring application context
ServletContext servletContext = event.getServletContext();
springContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
...
}
...
}
I've added to my test class:
#Before
public void mockInit() {
MockServletContext mockContext = new MockServletContext();
new MyListener().contextInitialized(new ServletContextEvent(mockContext));
}
But the springContext variable in contextInitialized is null.
I was missing a couple of things. 1st, my test class didn't have #WebAppConfiguration
#WebAppConfiguration
public class ClassAbcTest extends TestBase {
...
}
Second, I needed to set the root application context:
#Autowired
private WebApplicationContext webAppContext;
#Before
public void mockInit() {
MockServletContext mockServletContext = new MockServletContext();
mockServletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, webAppContext);
new MyListener().contextInitialized(new ServletContextEvent(
mockServletContext));
}
I realise this is an old question but I accidentally found an answer to this that isn't shown here.
I'm using an Embedded Apache FTP Server in my Spring Boot app that is initialised by a #WebListener
The Web Listener class was being missed and no FTP Connections possible during Junit tests.
The following annotation fixed it
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
I'd hope this answer helps anyone who stumbles across this page the same way it helped me.