Welcome back to my series on Java for .NET Developers. This series is intended to show how to migrate your C# skills over to Java. In the last post, we showed how to make a simple Hello World API and inject a Greeting service into the controller. We looked at the Spring Boot equivalent of a .NET Web API project. Now lets look at adding a simple data package.
One thing .NET developers have hated about Java is the lack of property syntax (i.e. “{get;set;}) and the need to verbosely write getters and setters. However, this only applies to classes, Java has the concept of a record which is very similar, and makes for concise code.
Let’s say we have the following C# data record:
namespace HelloWorldNet.Data;
public readonly record struct Greeting(string Language, string Message);
The Java equivalent of this is a record as well. However, naming conventions in Java are lowercase field names for the record, and it’s not a struct, it will live on the heap unline the C# record. It is immutable however. Here’s the Java record:
package com.example.helloworld.data;
public record Greeting (String language, String message){}
Just for kicks, here’s how this looks in Kotlin:
package com.example.helloworld.data
data class KotlinGreeting(
val language: String,
val message: String
);
The nice thing about Kotlin is that you can add Kotlin classes into your Java project as you like, but you have to configure it with Maven/Gradle… I think IntelliJ makes it super easy, but for now I’m going to stick to pure Java. Note that Kotlin supports the same type of nullable syntax as C#. For immutable properties they are declared as val, for mutable properties they are defined as var. IntelliJ also does this cool thing where it will convert your Java project to Kotlin, as JetBrains created the Kotlin langauge as well.
To add this into the previous project, it’s as simple as can be. Just update the interface to pass the Greeting class instead of the string, and update the code to use the data class, something like this:
@Service
class GreetingService implements IGreetingService {
public Greeting Greet() {
return new Greeting("fr-ca", "Bonjour Hi!");
}
}
In the HelloController:
@GetMapping("/hello")
public Greeting HelloWorld()
{
return greetingService.Greet();
}
With that, the HTTP GET request to //localhost:8080/hello will return a JSON response like the following: :
{"language":"fr-ca","message":"Bonjour Hi!"}
Now that we have a simple implementation we have enough to write the simplest of unit tests. At this point since the greetingservice is static and just returns “Bonjour Hi!” the only thing valuable to test is the controller– the controller should return what the service generates. In .NET we’d use NSubstitute (RIP Moq) and we’d specify the returns, it’s similar in Java but the pattern is slightly different. If you’re using IntelliJ Idea, you can simply ask AI tools for the syntax, which makes converting from .NET much easier than several years ago. To create a test for class “Foo”, call the test class “FooTest”. So for this sample, create a class called “HelloControllerTest” in the test package, right next to the application test that was generated with the project.
First, add the @Mock annotation above the mock, with an @InjectMocks annotation above the object we’re testing and injecting the mocks into, like the following. This lets you write tests for the test subject (marked with “@InjectMocks”) with all the dependencies marked with “@Mock”.
@Mock
private IGreetingService greetingService;
@InjectMocks
private HelloController controller;
The nice thing about this is you don’t have to call the constructor, the mock framework will automatically construct it when you declare all the mocks.
With that setup, we can add a simple test, like the following:
@Test
void testHelloWorld_ReturnsCorrectGreeting() {
// Arrange
Greeting expectedGreeting = new Greeting("English", "Hello, World!");
when(greetingService.Greet()).thenReturn(expectedGreeting);
// Act
Greeting actualGreeting = helloController.HelloWorld();
// Assert
assertEquals(expectedGreeting.language(), actualGreeting.language());
assertEquals(expectedGreeting.message(), actualGreeting.message());
}
Here’s a full listing of the test classs I have:
package com.example.helloworld;
import com.example.helloworld.HelloController;
import com.example.helloworld.data.Greeting;
import com.example.helloworld.services.IGreetingService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HelloControllerTest {
@Mock
private IGreetingService greetingService;
@InjectMocks
private HelloController helloController;
@Test
void testHelloWorld_ReturnsCorrectGreeting() {
// Arrange
Greeting expectedGreeting = new Greeting("English", "Hello, World!");
when(greetingService.Greet()).thenReturn(expectedGreeting);
// Act
Greeting actualGreeting = helloController.HelloWorld();
// Assert
assertEquals(expectedGreeting.language(), actualGreeting.language());
assertEquals(expectedGreeting.message(), actualGreeting.message());
}
@Test
void testHelloWorld_DifferentLanguage() {
// Arrange
Greeting expectedGreeting = new Greeting("Spanish", "¡Hola, Mundo!");
when(greetingService.Greet()).thenReturn(expectedGreeting);
// Act
Greeting actualGreeting = helloController.HelloWorld();
// Assert
assertEquals("Spanish", actualGreeting.language());
assertEquals("¡Hola, Mundo!", actualGreeting.message());
}
}
Here’s the common annotations we’ll using in Java for testing:
- – Marks a method as a test method
@Test
- – Creates a mock object (Mockito)
@Mock
- – Injects mock objects into the target class
@InjectMocks
With that, we’ve written a simple “baby’s first Java API” demonstrating very simple data packages, automatic JSON support, dependency injection, and unit testing. I think this is about as interesting as we can get with “hello, world” so in the next post we’ll move on to more complex solutions.
With that, you can see all the code for these samples on github here: https://github.com/danlarson/helloworldJava
Leave a Reply