1

I run a small Hello! project using with . It seems to work but I'm not sure how to actually write a test that tests that the classes are reloading after a change.

I can open the code in , start it with , make a change in a java method, compile the class and successfully(?) without restarting see

spring-boot-loaded [bootRun]: 1 class reloaded

and my change appears if I reload the browser. The code I can hotswap manually in IntelliJ is

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings FOO from Spring Boot!"; //I can hotswap this output if I recompile
    }

}

I can run it and manually hotswap java code in IntelliJ IDEA, if I compile the code. The way I run it from CLI is gradle bootRun and it is a copy of the reference project from the docs. I don't know how to run the test from the CLI in Ubuntu.

IIUC, the tests don't test reloading, only that it is running. I found test in spring loaded repo for method reloading but it seems old and many files. Can I make it simple and from the command line or is it only IDEs e.g. IntelliJ IDEA that supports hotswap?

Can I write a test that tests that reloading is working?

My test codes are

HelloControllerIT

package hello;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

import java.net.URL;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0"})
public class HelloControllerIT {

    @Value("${local.server.port}")
    private int port;

    private URL base;
    private RestTemplate template;

    @Before
    public void setUp() throws Exception {
        this.base = new URL("http://localhost:" + port + "/");
        template = new TestRestTemplate();
    }

    @Test
    public void getHello() throws Exception {
        ResponseEntity<String> response = template.getForEntity(base.toString(), String.class);
        assertThat(response.getBody(), equalTo("Greetings from Spring Boot!"));
    }
}

HelloControllerTest

package hello;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
public class HelloControllerTest {

    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
    }

    @Test
    public void getHello() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("Greetings from Spring Boot!")));
    }
}

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {

        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.4.3.RELEASE"
        classpath 'org.springframework:springloaded:1.2.6.RELEASE'
        classpath 'io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE'
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'rebel'

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath group: 'org.zeroturnaround', name: 'gradle-jrebel-plugin', version: '1.1.3'
    }
}
jar {
    baseName = 'gs-spring-boot'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    // tag::jetty[]
    compile("org.springframework.boot:spring-boot-starter-web") {
        exclude module: "spring-boot-starter-tomcat"
    }
    compile("org.springframework.boot:spring-boot-starter-jetty")
    // end::jetty[]
    // tag::actuator[]
    compile("org.springframework.boot:spring-boot-starter-actuator")

    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("junit:junit")

}

// change default IntelliJ output directory for compiling classes
idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}
Niklas Rosencrantz
  • 25,640
  • 75
  • 229
  • 424
  • 1
    Interesting question. You could copy a pre-compiled .class file into /classes/ and see if the change is picked up. Personally I don't think I would use spring-loaded today. In my mind deployment paradigms are moving towards container technologies (docker), so you replace the container rather than the code inside it. – Klaus Groenbaek Jan 14 '17 at 23:38
  • @KlausGroenbaek That's a good idea if I replace the .class file. I will try that and also consider the container alternatives you mention. – Niklas Rosencrantz Jan 15 '17 at 00:05
  • 1
    What is your motive for using spring-loaded? It offers more flexibility that debug hot-swap, but how often do you actually need that. On very large projects, where restarting an app take a long time, it may be feasible, but I would only use it from development, never in production (I would probably even remove the jar from the classpath) – Klaus Groenbaek Jan 15 '17 at 00:14
  • @KlausGroenbaek Save time during development. I had projects that used spring framework and I had to restart jetty 50 times every day. – Niklas Rosencrantz Jan 15 '17 at 00:16
  • 1
    If you write (MockMvc) tests of your spring Controllers you typically only need to start you webserver once for every feature ;) – Klaus Groenbaek Jan 15 '17 at 00:20
  • I often must change a web view. Then it would be nice if I'm not forced to restart tomcat or jetty or whatever server is used. It's often an entire prototype (webapp) in demo mode. My method has been to start jetty with `nohup mvn:jetty` and I would like to make it easier to change web views that I previously did with JSP and now I should do it with thymeleaf. – Niklas Rosencrantz Jan 15 '17 at 00:28
  • 1
    I always run Web apps directly from IntelliJ, which is configured to reload the app on frame deactivation, so when I tab to my browser all views are automatically reloaded. Also after I discovered Thymeleaf I have stopped using JSP, since Thymeleaf augments html files, so you can do the basic layout of the page without a backend. – Klaus Groenbaek Jan 15 '17 at 21:56

0 Answers0