Main Tutorials

Spring Boot Hello World Example – Mustache

A Spring Boot web application example, using embedded Tomcat + Mustache template engine, and package as an executable JAR file.

Technologies used :

  1. Spring Boot 1.5.2.RELEASE
  2. Spring 4.3.7.RELEASE
  3. jmustache 1.13
  4. Thymeleaf 2.1.5.RELEASE
  5. Tomcat Embed 8.5.11
  6. Maven 3
  7. Java 8
Note
Spring Boot uses jmustache to integrate Mustache as template engine.

1. Project Directory

2. Project Dependencies

Declares spring-boot-starter-mustache, it will get anything you need to develop a Spring + Mustache web application.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<artifactId>spring-boot-web-mustache</artifactId>
	<packaging>jar</packaging>
	<name>Spring Boot Web Mustache Example</name>
	<description>Spring Boot Web Mustache Example</description>
	<url>https://www.mkyong.com</url>
	<version>1.0</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
	</parent>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mustache</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- hot swapping, live reload -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>

		<!-- Optional, for bootstrap -->
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>bootstrap</artifactId>
			<version>3.3.7</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<!-- Package as an executable jar/war -->
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Display project dependencies :


$ mvn dependency:tree

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Spring Boot Web Mustache Example 1.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ spring-boot-web-mustache ---
                               [INFO] org.springframework.boot:spring-boot-web-mustache:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-mustache:jar:1.5.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.5.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.2.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.11:compile
[INFO] |  |  |  +- org.slf4j:jcl-over-slf4j:jar:1.7.24:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.24:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.24:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.17:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-web:jar:1.5.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.5.2.RELEASE:compile
[INFO] |  |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.11:compile
[INFO] |  |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.11:compile
[INFO] |  |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.11:compile
[INFO] |  |  +- org.hibernate:hibernate-validator:jar:5.3.4.Final:compile
[INFO] |  |  |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |  |  +- org.jboss.logging:jboss-logging:jar:3.3.0.Final:compile
[INFO] |  |  |  \- com.fasterxml:classmate:jar:1.3.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.7:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.8.7:compile
[INFO] |  |  +- org.springframework:spring-web:jar:4.3.7.RELEASE:compile
[INFO] |  |  |  +- org.springframework:spring-aop:jar:4.3.7.RELEASE:compile
[INFO] |  |  |  \- org.springframework:spring-beans:jar:4.3.7.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-webmvc:jar:4.3.7.RELEASE:compile
[INFO] |  |     \- org.springframework:spring-expression:jar:4.3.7.RELEASE:compile
[INFO] |  \- com.samskivert:jmustache:jar:1.13:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:1.5.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:1.5.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:1.5.2.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.2.0:test
[INFO] |  |  +- net.minidev:json-smart:jar:2.2.1:test
[INFO] |  |  |  \- net.minidev:accessors-smart:jar:1.1:test
[INFO] |  |  |     \- org.ow2.asm:asm:jar:5.0.3:test
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.24:compile
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:2.6.0:test
[INFO] |  +- org.mockito:mockito-core:jar:1.10.19:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.1:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.4.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:4.3.7.RELEASE:compile
[INFO] |  \- org.springframework:spring-test:jar:4.3.7.RELEASE:test
[INFO] +- org.springframework.boot:spring-boot-devtools:jar:1.5.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot:jar:1.5.2.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-context:jar:4.3.7.RELEASE:compile
[INFO] |  \- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.2.RELEASE:compile
[INFO] \- org.webjars:bootstrap:jar:3.3.7:compile
[INFO]    \- org.webjars:jquery:jar:1.11.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.796 s
[INFO] Finished at: 2017-04-19T11:46:09+08:00
[INFO] Final Memory: 22M/437M
[INFO] ------------------------------------------------------------------------

3. Spring Boot

3.1 Create a @SpringBootApplication class. Run this class to start the Spring Boot web application.

SpringBootWebApplication.java

package com.mkyong;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootWebApplication {

	public static void main(String[] args) throws Exception {
		SpringApplication.run(SpringBootWebApplication.class, args);
	}

}

3.2 A simple controller class.

WelcomeController.java

package com.mkyong;

import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class WelcomeController {

	// inject via application.properties
	@Value("${app.welcome.message}")
	private String MESSAGE = "";

	@Value("${app.welcome.title}")
	private String TITLE = "";

	@RequestMapping("/")
	public String welcome(Map<String, Object> model) {
		model.put("title", TITLE);
		model.put("message", MESSAGE);
		return "welcome";
	}

	// test 5xx errors
	@RequestMapping("/5xx")
	public String ServiceUnavailable() {
		throw new RuntimeException("ABC");
	}

}

4. Mustache + Resources + Static files

4.1 For Mustache template files, put in src/main/resources/templates/

src/main/resources/templates/layout/header.html

<!DOCTYPE HTML>
<head>

<title>{{title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<link rel="stylesheet" type="text/css" href="webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="css/main.css" />

</head>
<!--  this is header -->
src/main/resources/templates/layout/footer.html

<!--  this is footer -->
</html>
src/main/resources/templates/welcome.html

{{>layout/header}}
<body>

	<nav class="navbar navbar-inverse">
		<div class="container">
			<div class="navbar-header">
				<a class="navbar-brand" href="#">Spring Boot</a>
			</div>
		</div>
	</nav>

	<div class="container">

		<div class="starter-template">
			<h1>Spring Boot Web Mustache Example</h1>
			<h2>
				{{message}}
			</h2>
		</div>

	</div>
	<!-- /.container -->

	<script type="text/javascript" src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>

</body>
{{>layout/footer}}

4.2 For static files like CSS or Javascript, put in /src/main/resources/static/

/src/main/resources/static/css/main.css

h1{
	color:#0000FF;
}

h2{
	color:#FF0000;
}

4.3 For error templates.

Note
Read this Spring Boot – Error Handling to understand how the default error mapping page works.
src/main/resources/templates/error.html

<!DOCTYPE html>

<html lang="en">

<body>
	Something went wrong: {{status}} {{error}}
</body>

</html>
src/main/resources/templates/error/5xx.html

<!DOCTYPE html>

<html lang="en">

<body>
	I'm a 5xx
</body>

</html>

4.4 For properties files, put in /src/main/resources/

/src/main/resources/application.properties

app.welcome.message: Hello Mkyong
app.welcome.title: Spring Boot Mustache Hello World Example
Note
Read this Spring Boot Serving static content to understand the resource mapping.

5. Unit Test

5.1 Unit test example to test above Spring Boot web application.

MustacheApplicationTests

package com.mkyong;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Arrays;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
public class MustacheApplicationTests {

	@Autowired
	private TestRestTemplate restTemplate;

	@Test
	public void testMainPage() throws Exception {

		ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
		assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
		assertThat(entity.getBody()).contains("Hello Mkyong");

	}

	@Test
	public void test404Page() throws Exception {

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
		HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
		ResponseEntity<String> responseEntity = this.restTemplate.exchange("/uri-not-exist", HttpMethod.GET,
				requestEntity, String.class);
		assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
		assertThat(responseEntity.getBody()).contains("Something went wrong: 404 Not Found");

	}

	@Test
	public void test5xxPage() throws Exception {

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
		HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
		ResponseEntity<String> responseEntity = this.restTemplate.exchange("/5xx", HttpMethod.GET, requestEntity,
				String.class);
		assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
		assertThat(responseEntity.getBody()).contains("I'm a 5xx");

	}

}

6. Demo

Note
In IDE, run the @SpringBootApplication annotated class, and the entire Spring Boot application will be started.

6.1 Start the Spring Boot web app.

Terminal

project$ mvn spring-boot:run

//...
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-04-19 12:17:38.014  INFO 6232 --- [  restartedMain] com.mkyong.SpringBootWebApplication      : Starting SpringBootWebApplication on MKYONG-WIN10 with PID 6232 (C:\spring-boot\spring-boot-examples\spring-boot-web-mustache\target\classes started by mkyong in C:\spring-boot\spring-boot-examples\spring-boot-web-mustache)
2017-04-19 12:17:38.015  INFO 6232 --- [  restartedMain] com.mkyong.SpringBootWebApplication      : No active profile set, falling back to default profiles: default
2017-04-19 12:17:38.074  INFO 6232 --- [  restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@af1619: startup date [Wed Apr 19 12:17:38 SGT 2017]; root of context hierarchy
2017-04-19 12:17:39.352  INFO 6232 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-04-19 12:17:39.370  INFO 6232 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-04-19 12:17:39.372  INFO 6232 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-04-19 12:17:39.481  INFO 6232 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-04-19 12:17:39.481  INFO 6232 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1410 ms
2017-04-19 12:17:39.649  INFO 6232 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-04-19 12:17:39.658  INFO 6232 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-04-19 12:17:39.659  INFO 6232 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-04-19 12:17:39.659  INFO 6232 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-04-19 12:17:39.660  INFO 6232 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-04-19 12:17:39.938  INFO 6232 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@af1619: startup date [Wed Apr 19 12:17:38 SGT 2017]; root of context hierarchy
2017-04-19 12:17:39.997  INFO 6232 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String com.mkyong.WelcomeController.welcome(java.util.Map<java.lang.String, java.lang.Object>)
2017-04-19 12:17:39.997  INFO 6232 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/5xx]}" onto public java.lang.String com.mkyong.WelcomeController.ServiceUnavailable()
2017-04-19 12:17:40.006  INFO 6232 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-04-19 12:17:40.006  INFO 6232 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-04-19 12:17:40.041  INFO 6232 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-19 12:17:40.041  INFO 6232 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-19 12:17:40.090  INFO 6232 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-19 12:17:40.332  INFO 6232 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-04-19 12:17:40.373  INFO 6232 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-04-19 12:17:40.427  INFO 6232 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-19 12:17:40.435  INFO 6232 --- [  restartedMain] com.mkyong.SpringBootWebApplication      : Started SpringBootWebApplication in 2.737 seconds (JVM running for 3.124)

6.2 Access http://localhost:8080

6.2 Access http://localhost:8080/uri-not-exist

6.2 Access http://localhost:8080/5xx

6. Build an executable JAR

6.1 Package the project to create an executable JAR file.


project$ mvn clean package

6.2 Run It, access http://localhost:8080 again.


project$ java -jar target/spring-boot-web-mustache-1.0.jar

Download Source Code

Download it – spring-boot-web-mustache.zip (11 KB)

References

  1. Mustache template engine
  2. jmustache – A Java implementation of the Mustache templating language
  3. Spring Boot – Error Handling
  4. Spring Boot – Static content
  5. Deploying Spring Boot Applications
  6. Spring MVC – Inlucde CSS file
  7. Spring Boot – Developer tools
  8. Serving Web Content with Spring MVC
  9. Spring Boot Hello World Example – JSP

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
10 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
dsd
6 years ago

}

@Test
public void test5xxPage() throws Exception {

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpEntity requestEntity = new HttpEntity(headers);
ResponseEntity responseEntity = this.restTemplate.exchange(“/5xx”, HttpMethod.GET, requestEntity,
String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(responseEntity.getBody()).contains(“I’m a 5xx”);

}

Sujeet Singh
6 years ago

Is scope required as given in post. It was not working in my case.

org.springframework.boot
spring-boot-starter-test
test

Armando Couto
6 years ago

Achei muito massa.

mkyong
6 years ago
Reply to  Armando Couto

Sorry, what do you mean?

Armando Couto
6 years ago
Reply to  mkyong

Maybe say that I found the post very good! 🙂

jerome villiseck
5 years ago

Be careful to respect the tree structure for the html pages located in the template package

jerome villiseck
5 years ago

Warning : This example did not work under java 10 but under java 1.8. Change the JDK to 1.8 if you have errors when you launch the command
mvn spring-boot:run

Fans
5 years ago

dependencies {
compile ‘org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE’
// compile ‘org.springframework.boot:spring-boot-starter-jetty:2.1.0.RELEASE’
compile ‘org.springframework.boot:spring-boot-starter-mustache:2.1.0.RELEASE’

testCompile group: ‘junit’, name: ‘junit’, version: ‘4.11’
}

spring.mustache.suffix=.html

Rafael Cornelio Bautista
5 years ago

Hi, Iam geeting this: Exception in thread “main” java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader canno
t be cast to java.base/java.net.URLClassLoader
at org.springframework.boot.devtools.restart.DefaultRestartInitializer.getUrls(DefaultRestartInitializer.java:93
)
at org.springframework.boot.devtools.restart.DefaultRestartInitializer.getInitialUrls(DefaultRestartInitializer.
java:56)

Madu
5 years ago

Hi Mkyong,
I wrote my own application with same content defined above, but I keep getting errors while running test

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.064 sec <<< FAILURE! – in org.test.sampleProject.MustacheApplicationTests
initializationError(org.test.sampleProject.MustacheApplicationTests) Time elapsed: 0.008 sec <<< ERROR!
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=…) with your test

I resolve this error by adding @SpringBootApplication to test class. But keep getting test failures always return 404