This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/************************************************/ | |
DOMAIN OBJECTS | |
*************************************************/ | |
/*********** Coordinates.java *******************/ | |
package com.pelssers.domain; | |
public class Coordinates { | |
private Double latitude; | |
private Double longitude; | |
public Coordinates(Double latitude, Double longitude) { | |
this.latitude = latitude; | |
this.longitude = longitude; | |
} | |
public Double getLatitude() { | |
return latitude; | |
} | |
public Double getLongitude() { | |
return longitude; | |
} | |
} | |
/*********** Weather.java *******************/ | |
package com.pelssers.domain; | |
import java.util.Set; | |
public class Weather { | |
private Double temperature; | |
private Set<WeatherType> weatherTypes; | |
public Weather(Double temperature, Set<WeatherType> weatherTypes) { | |
this.temperature = temperature; | |
this.weatherTypes = weatherTypes; | |
} | |
public Double getTemperature() { | |
return temperature; | |
} | |
public Set<WeatherType> getWeatherTypes() { | |
return weatherTypes; | |
} | |
@Override | |
public String toString() { | |
return "{temperature: " + getTemperature().toString() + | |
", weatherTypes: [" + weatherTypes.toString() + "]"; | |
} | |
} | |
/*********** WeatherType.java *******************/ | |
package com.pelssers.domain; | |
public enum WeatherType { | |
RAINY, DRY, SUNNY, CLOUDY | |
} | |
/************************************************/ | |
SERVICE INTERFACES | |
*************************************************/ | |
/*********** IHumidityService.java *************/ | |
package com.pelssers.services; | |
import java.util.Date; | |
import com.pelssers.domain.Coordinates; | |
public interface IHumidityService { | |
Double predictHumidity(Date date, Coordinates coordinates); | |
} | |
/*********** ISatteliteService.java *************/ | |
package com.pelssers.services; | |
import java.util.Date; | |
import com.pelssers.domain.Coordinates; | |
public interface ISatteliteService { | |
boolean isCloudy(Date date, Coordinates coordinates); | |
} | |
/*********** ITemperatureService.java *************/ | |
package com.pelssers.services; | |
import java.util.Date; | |
import com.pelssers.domain.Coordinates; | |
public interface ITemperatureService { | |
Double predictTemperature(Date date, Coordinates coordinates); | |
} | |
/*********** IWeatherForecastService.java *************/ | |
package com.pelssers.services; | |
import java.util.Date; | |
import com.pelssers.domain.Coordinates; | |
import com.pelssers.domain.Weather; | |
public interface IWeatherForecastService { | |
Weather getForecast(Date date, Coordinates coordinates); | |
} | |
/************************************************/ | |
SERVICE IMPLEMENTATION(S) | |
*************************************************/ | |
/*********** WeatherForecastService.java *************/ | |
package com.pelssers.services.impl; | |
import java.util.Date; | |
import java.util.HashSet; | |
import java.util.Set; | |
import javax.inject.Inject; | |
import org.springframework.stereotype.Component; | |
import com.pelssers.domain.Coordinates; | |
import com.pelssers.domain.Weather; | |
import com.pelssers.domain.WeatherType; | |
import com.pelssers.services.ISatteliteService; | |
import com.pelssers.services.ITemperatureService; | |
import com.pelssers.services.IWeatherForecastService; | |
import com.pelssers.services.IHumidityService; | |
@Component() | |
public class WeatherForecastService implements IWeatherForecastService { | |
@Inject | |
private ITemperatureService temperatureService; | |
@Inject | |
private IHumidityService humidityService; | |
@Inject | |
private ISatteliteService satteliteService; | |
@Override | |
public Weather getForecast(Date date, Coordinates coordinates) { | |
final Double predictedTemperature = temperatureService.predictTemperature(date, coordinates); | |
final Double predictedHumidity = humidityService.predictHumidity(date, coordinates); | |
final boolean isCloudy = satteliteService.isCloudy(date, coordinates); | |
//here starts our actual business logic | |
Set<WeatherType> weatherTypes = new HashSet<WeatherType>(); | |
weatherTypes.add(isCloudy? WeatherType.CLOUDY : WeatherType.SUNNY); | |
weatherTypes.add(predictedHumidity > 50 ? WeatherType.RAINY : WeatherType.DRY); | |
return new Weather(predictedTemperature, weatherTypes); | |
} | |
} | |
/************************************************/ | |
UNIT TEST | |
*************************************************/ | |
package com.pelssers.services.impl; | |
import java.util.Date; | |
import org.junit.Assert; | |
import org.junit.Before; | |
import org.junit.Test; | |
import org.mockito.InjectMocks; | |
import org.mockito.Mock; | |
import org.mockito.MockitoAnnotations; | |
import static org.mockito.Matchers.*; | |
import static org.mockito.Mockito.*; | |
import com.google.common.collect.ImmutableSet; | |
import com.pelssers.domain.Coordinates; | |
import com.pelssers.domain.Weather; | |
import com.pelssers.domain.WeatherType; | |
import com.pelssers.services.IHumidityService; | |
import com.pelssers.services.ISatteliteService; | |
import com.pelssers.services.ITemperatureService; | |
import com.pelssers.services.IWeatherForecastService; | |
public class WeatherForecastServiceTest { | |
/** | |
* As our weather forecast service depends on lots of other services | |
* we want to mock the services we depend on and test our business logic | |
* in isolation. As you can see we no longer need setters and getters in | |
* WeatherForecastService, not for dependency injection and neither for unit testing | |
*/ | |
@InjectMocks | |
private IWeatherForecastService weatherForecastService = new WeatherForecastService(); | |
@Mock | |
private IHumidityService humidityService; | |
@Mock | |
private ITemperatureService temperatureService; | |
@Mock | |
private ISatteliteService satteliteService; | |
private Coordinates coordinates; | |
private Date forecastDate; | |
private Double expectedTemperature; | |
@Before | |
public void setUp() { | |
//instruct mockito to process all annotations | |
MockitoAnnotations.initMocks(this); | |
forecastDate = new Date(2013, 10 , 1); | |
coordinates = new Coordinates(14.5, 123.4); | |
expectedTemperature = 16D; | |
when(temperatureService.predictTemperature(any(Date.class), any(Coordinates.class))) | |
.thenReturn(expectedTemperature); | |
} | |
@Test | |
public void testRainyAndCloudyWeather() { | |
Weather expectedWeather = new Weather(expectedTemperature, | |
ImmutableSet.of(WeatherType.RAINY, WeatherType.CLOUDY)); | |
when(humidityService.predictHumidity(any(Date.class), any(Coordinates.class))) | |
.thenReturn(60D); | |
when(satteliteService.isCloudy(any(Date.class), any(Coordinates.class))) | |
.thenReturn(true); | |
testWeather(expectedWeather); | |
} | |
@Test | |
public void testDryAndSunnyWeather() { | |
Weather expectedWeather = new Weather(expectedTemperature, | |
ImmutableSet.of(WeatherType.DRY, WeatherType.SUNNY)); | |
when(humidityService.predictHumidity(any(Date.class), any(Coordinates.class))) | |
.thenReturn(40D); | |
when(satteliteService.isCloudy(any(Date.class), any(Coordinates.class))) | |
.thenReturn(false); | |
testWeather(expectedWeather); | |
} | |
private void testWeather(Weather expectedWeather) { | |
Weather actualWeather = weatherForecastService.getForecast(forecastDate, coordinates); | |
//verify our services are called once | |
verify(humidityService, times(1)).predictHumidity(forecastDate, coordinates); | |
verify(temperatureService, times(1)).predictTemperature(forecastDate, coordinates); | |
verify(satteliteService, times(1)).isCloudy(forecastDate, coordinates); | |
Assert.assertEquals(expectedWeather.getTemperature(), actualWeather.getTemperature()); | |
Assert.assertEquals(expectedWeather.getWeatherTypes(), actualWeather.getWeatherTypes()); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> | |
<groupId>com.pelssers</groupId> | |
<artifactId>mockitodemo</artifactId> | |
<version>0.0.1-SNAPSHOT</version> | |
<name>Mockito Demo</name> | |
<packaging>jar</packaging> | |
<properties> | |
<spring.version>3.1.2.RELEASE</spring.version> | |
</properties> | |
<dependencies> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-core</artifactId> | |
<version>${spring.version}</version> | |
<scope>compile</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-beans</artifactId> | |
<version>${spring.version}</version> | |
<scope>compile</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-context</artifactId> | |
<version>${spring.version}</version> | |
<scope>compile</scope> | |
</dependency> | |
<dependency> | |
<groupId>javax.inject</groupId> | |
<artifactId>javax.inject</artifactId> | |
<version>1</version> | |
</dependency> | |
<dependency> | |
<groupId>com.google.guava</groupId> | |
<artifactId>guava</artifactId> | |
<version>14.0.1</version> | |
</dependency> | |
<!-- Test Dependencies --> | |
<dependency> | |
<groupId>junit</groupId> | |
<artifactId>junit</artifactId> | |
<version>4.8.2</version> | |
<scope>test</scope> | |
</dependency> | |
<dependency> | |
<groupId>org.mockito</groupId> | |
<artifactId>mockito-core</artifactId> | |
<version>1.9.5</version> | |
<scope>test</scope> | |
</dependency> | |
</dependencies> | |
</project> |
Yay, test code :)
ReplyDeleteWhen I use mocks, I also verify that they are called in the test.
Example:
verify(temperatureService).predictTemperature(any(Date.class), any(Coordinates.class));
After refactoring you'll find out whether your mock is still needed or you accidentally introduced an extra call for this method.
yes... in the above case it might be overkill as they are always called of course. But if you conditionally use services you are completely right and your remark is more than valid.
ReplyDelete