/
Full complete versions of the code
Full complete versions of the code
FileLoader
LiveFileLoaderTest - not suitable for a build server and breaks the tenets of a good unit test
package com.celestial.mockito.filetodb;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
*
* @author selvy
*/
public class LiveFileLoaderTest
{
// The inital design is described in this test
/*
The weakness should be obvious? The file to be loaded and it's location
I use a shared network drive to run this code from different machines, normally
dev-ing from a PC. When I ran the code on the laptop from a cafe it
immediately failed because the C: on the laptop was completely different
to the PC, so the original file C:/tmp/KeyboardHandler.txt did not exist
THIS IS A GREAT EXAMPLE OF WHY THE UNIT TEST AND CUT SHOULD NOT BE STRONGLY
LINKED TO ANY IO - NETWORK, DB, AND FILE SYSTEM
*/
@Test
public void load_all_of_file_using_inbuilt_Files_type()
{
// arrange
String fileToLoad = "c:/tmp/kipstor-settings.js";
FileLoader cut = new FileLoader(fileToLoad);
int expectedBytesRead = 301;
// act
int bytesRead = cut.loadFile(fileToLoad);
// assert
assertEquals(expectedBytesRead, bytesRead);
}
}
FIleLoader implementation
package com.celestial.mockito.filetodb;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
/**
*
* @author selvy
*/
public class FileLoader
{
private class IntWrapper
{
public int value;
}
String fileToLoad;
List<String> lines = Collections.emptyList();
public FileLoader(String fileToLoad)
{
this.fileToLoad = fileToLoad;
}
int loadFile(String fname)
{
try
{
lines = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
}
catch (IOException e){}
return calculateFileSize();
}
private int calculateFileSize()
{
IntWrapper result = new IntWrapper();
lines.forEach(line -> {
result.value += line.length();
});
return result.value;
}
}
FileLoaderTest - suitable for a build pipeline and does not break the tenets of unit testing
package com.celestial.mockito.filetodb;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Test;
import static org.junit.Assert.*;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
/**
*
* @author selvy
*/
public class FileLoaderTest
{
// To use a different type of file system loader, pass a lambda to loadFile()
// as shown here
/*
int bytesRead = cut.loadFile((fname) ->
{
List<String> result = null;
try
{
result = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
}
catch (IOException e){}
return result;
});
*/
// Redesign the FileLoader so that the machenism to load files up can be
// passed in as a lambda - still titghtly coupled the file system
@Test
public void load_all_of_file_using_inbuilt_Files_type_as_lambda()
{
// arrange
String fileToLoad = "c:/tmp/KeyboardHandler.txt";
FileLoader cut = new FileLoader(fileToLoad);
int expectedBytesRead = 10; //1371;
List<String> pretendFileContent = new ArrayList<>();
pretendFileContent.add("Hello");
pretendFileContent.add("world");
MockedStatic<Files> ff = Mockito.mockStatic(Files.class);
ff.when(() -> Files.readAllLines(Paths.get(fileToLoad), StandardCharsets.UTF_8)).thenReturn(pretendFileContent);
// act
int bytesRead = cut.loadFile((fname) ->
{
List<String> result = null;
try
{
result = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
}
catch (IOException e){}
return result;
});
// assert
assertEquals(expectedBytesRead, bytesRead);
}
// Redesign has worked so this test uses a stub to simulate a file being
// loaded, it is passed in as lambda - We've decoupled ourselves from the
// filesystem
@Test
public void load_all_of_file_via_stub()
{
// arrange
String fileToLoad = "c:/tmp/KeyboardHandler.txt";
FileLoader cut = new FileLoader(fileToLoad);
int expectedBytesRead = 10;
// act
int bytesRead = cut.loadFile((fname) ->
{
List<String> result = new ArrayList<>();
result.add("Hello");
result.add("world");
return result;
});
// assert
assertEquals(expectedBytesRead, bytesRead);
}
}
FileLoader implementation code - refactored
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.celestial.mockito.filetodb;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
/**
*
* @author selvy
*/
public class FileLoader
{
class IntWrapper
{
int value;
}
String fileToLoad;
List<String> lines = Collections.emptyList();
public FileLoader(String fileToLoad)
{
this.fileToLoad = fileToLoad;
}
public List<String> getLines() {
return lines;
}
int loadFile(String fname)
{
try
{
lines = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
}
catch (IOException e)
{
}
return calculateFileSize();
}
int loadFile(ILoader func)
{
lines = func.loadFile(fileToLoad);
return calculateFileSize();
}
private int calculateFileSize()
{
IntWrapper result = new IntWrapper();
lines.forEach(line -> {
result.value += line.length();
});
return result.value;
}
}
DBConnector
LiveDBConnectorTest - not suitable for a build server and breaks the tenets of a good unit test
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.celestial.mockito.filetodb;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
/**
*
* @author selvy
*/
public class LiveDBConnectorTest
{
static String dbEndPoint = "jdbc:mysql://localhost:3306/";
static String dbName = "files";
// This test is not suitable for a build server. so we will need to stub
// IDbConnectionMgr
//@Ignore
@Test
public void open_connection() throws SQLException
{
// arrange
String dbEndPoint_ = "jdbc:mysql://localhost:3306/";
String dbName_ = "files";
IDbConnectionMgr connection = (endPoint, db) -> {
Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
return conn;
};
DBConnector cut = new DBConnector(connection);
// act
Connection conn = cut.openConnection(dbEndPoint_, dbName_);
// assert
assertNotNull(conn);
}
@Test
public void write_line_to_live_connection() throws SQLException
{
// arrange
IDbConnectionMgr connection = (endPoint, db) -> {
Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
return conn;
};
DBConnector cut = new DBConnector(connection);
cut.openConnection(dbEndPoint, dbName);
String lineToWrite = "be not changed by...";
int expectedLineNo = 1;
// act
// The idea of writeLine() is that each time a line is written to the DB
// it's line No should be returned. Line Nos start at 1
int lineNo = cut.writeLine(lineToWrite);
// assert
assertEquals(expectedLineNo, lineNo);
}
}
DBConnector - Initial implementation
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.celestial.mockito.filetodb;
import com.mysql.cj.xdevapi.PreparableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author selvy
*/
public class DBConnector
{
private IDbConnectionMgr connectionMgr;
private Connection theConnection;
private int fileId;
private static final String SQL_INSERT_LINE = "insert into files.line_in_file" +
" values (default, ?, ?);";
private static final String SQL_LINE_COUNT = "select count(*) from files.line_in_file;";
DBConnector(IDbConnectionMgr connection)
{
connectionMgr = connection;
}
Connection openConnection(String dbEndPoint, String dbName) throws SQLException
{
theConnection = connectionMgr.openConnection(dbEndPoint, dbName);
return theConnection;
}
int writeLine(String lineToWrite)
{
int result = 0;
fileId = 1;
try
{
PreparedStatement ps = theConnection.prepareStatement(SQL_INSERT_LINE);
ps.setInt(1, fileId);
ps.setString(2, lineToWrite);
ps.executeUpdate();
ps = theConnection.prepareStatement(SQL_LINE_COUNT);
ResultSet rs = ps.executeQuery();
while (rs.next()){
result = rs.getInt(1);
}
} catch (SQLException ex)
{
Logger.getLogger(DBConnector.class.getName()).log(Level.SEVERE, null, ex);
}
return result;
}
}
DBConnectorTest - suitable for a build pipeline and does not break the tenets of unit testing
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.celestial.mockito.filetodb;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import static org.mockito.Mockito.spy;
/**
*
* @author selvy
*/
public class DBConnectorTest
{
static String dbEndPoint = "jdbc:mysql://localhost:3306/";
static String dbName = "files";
static Connection sqlConn = Mockito.mock(Connection.class);
// Mockito does not like the line below to be present in each test method
// it registers the static method with the current test thread once, if this line
// is called more than once in a unit test file, a Mockito exception is thrown
//
// The live DB connection code has been moved to it's own test file because
// it was pickup the mocked DriverManager and returning null because the
// params being passed in (real values) did not match the stubbed values
static MockedStatic<DriverManager> mockedDMgr = Mockito.mockStatic(DriverManager.class);
@BeforeClass
static public void beforeAllTestsDo()
{
// Because we are mocking the static, registration of the method should
// only occur once for the unit test file
mockedDMgr.when(() -> DriverManager.getConnection(dbEndPoint+dbName, "root", "ppp")).thenReturn(sqlConn);
}
@Test
public void open_connection_mocked() throws SQLException
{
// arrange
IDbConnectionMgr connection = (endPoint, db) -> {
Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
return conn;
};
DBConnector cut = new DBConnector(connection);
DBConnector spyCut = spy(cut);
int expectedLineNoCount = 1;
Mockito.doReturn(1).when(spyCut).queryLinesInDB();
// act
Connection conn = spyCut.openConnection(dbEndPoint, dbName);
int lineNoResult = spyCut.getLineCount();
// assert
assertNotNull(conn);
assertEquals(expectedLineNoCount, lineNoResult);
}
@Test
public void write_line_to_mocked_connection() throws SQLException
{
// arrange
IDbConnectionMgr connection = (endPoint, db) -> {
Connection conn = DriverManager.getConnection(endPoint+db, "root", "ppp");
return conn;
};
DBConnector cut = new DBConnector(connection);
DBConnector spyCut = spy(cut);
Mockito.doReturn(0).when(spyCut).queryLinesInDB();
PreparedStatement ps = Mockito.mock( PreparedStatement.class );
Mockito.when(sqlConn.prepareStatement(Mockito.any())).thenReturn(ps);
spyCut.openConnection(dbEndPoint, dbName);
String lineToWrite = "be not changed by...";
int expectedLineNo = 1;
// act
// The idea of writeLine() is that achg time a line is written to the DB
// it's line No should be returned. Line Nos start at 1
int lineNo = spyCut.writeLine(lineToWrite);
// assert
assertEquals(expectedLineNo, lineNo);
}
}
DBConnector - refactored
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.celestial.mockito.filetodb;
import com.mysql.cj.xdevapi.PreparableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author selvy
*/
public class DBConnector
{
private final IDbConnectionMgr connectionMgr;
private Connection theConnection;
private int fileId = 1;
private int itsLineCount;
private static final String SQL_INSERT_LINE = "insert into files.line_in_file" +
" values (default, ?, ?);";
private static final String SQL_LINE_COUNT = "select count(*) from files.line_in_file;";
DBConnector(IDbConnectionMgr connection)
{
connectionMgr = connection;
}
Connection openConnection(String dbEndPoint, String dbName) throws SQLException
{
theConnection = connectionMgr.openConnection(dbEndPoint, dbName);
itsLineCount = queryLinesInDB();
return theConnection;
}
public int getLineCount()
{
return itsLineCount;
}
public void updateLineCount()
{
updateLineCount(getLineCount() + 1);
}
public void updateLineCount(int value)
{
itsLineCount = value;
}
public int getFileId()
{
return fileId;
}
int queryLinesInDB()
{
try
{
PreparedStatement ps = theConnection.prepareStatement(SQL_LINE_COUNT);
ResultSet rs = ps.executeQuery();
while (rs.next()){
itsLineCount = rs.getInt(1);
}
} catch (SQLException ex)
{
Logger.getLogger(DBConnector.class.getName()).log(Level.SEVERE, null, ex);
}
return itsLineCount;
}
public int writeLine(String lineToWrite)
{
int fileId = getFileId();
try
{
PreparedStatement ps = theConnection.prepareStatement(SQL_INSERT_LINE);
ps.setInt(1, fileId);
ps.setString(2, lineToWrite);
ps.executeUpdate();
updateLineCount();
} catch (SQLException ex)
{
Logger.getLogger(DBConnector.class.getName()).log(Level.SEVERE, null, ex);
}
return getLineCount();
}
}
Interfaces
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.celestial.mockito.filetodb;
import java.util.List;
/**
*
* @author selvy
*/
@FunctionalInterface
public interface ILoader
{
List<String> loadFile(String fname);
}
, multiple selections available,
Related content
Discovery through unit testing
Discovery through unit testing
Read with this
TDD FileLoader v1.0.
TDD FileLoader v1.0.
More like this
TDD FileLoader v1.2.
TDD FileLoader v1.2.
More like this
TDD FileLoader v1.4.
TDD FileLoader v1.4.
More like this
TDD FileLoader v1.3.
TDD FileLoader v1.3.
More like this
QL-4) Using TDD to design production code.
QL-4) Using TDD to design production code.
More like this