Writing units tests for the view models of the WeekViewController
class is just as easy as writing unit tests for the DayViewViewModel
struct. We start with the unit tests for the WeekViewViewModel
struct.
Create a new XCTestCase
subclass and name the file WeekViewViewModelTests.swift.
Add an import statement for the Cloudy module and define a property for the view model like we did in the previous chapter. The approach we take is identical to the approach we took in the previous chapter. The type of the property is an implicitly unwrapped optional, WeekViewViewModel!
.
WeekViewViewModelTests.swift
1
import
XCTest
2
@
testable
import
Cloudy
3
4
class
WeekViewViewModelTests
:
XCTestCase
{
5
6
//
MARK:
- Properties
7
8
var
viewModel
:
WeekViewViewModel
!
9
10
//
MARK:
- Set Up & Tear Down
11
12
override
func
setUp
()
{
13
super
.
setUp
()
14
}
15
16
override
func
tearDown
()
{
17
super
.
tearDown
()
18
}
19
20
}
In the setUp()
method, we load the same stub from the unit testing bundle, instantiate a WeatherData
instance with it, and use the value of the dailyData
property to instantiate the view model. Remember that the dailyData
property is of type [WeatherDayData]
.
WeekViewViewModelTests.swift
1
override
func
setUp
()
{
2
super
.
setUp
()
3
4
// Load Stub
5
let
data
=
loadStubFromBundle
(
withName
:
"darksky"
,
extension
:
"j\
6
son"
)
7
let
weatherData
:
WeatherData
=
try
!
JSONDecoder
.
decode
(
data
:
dat
\
8
a
)
9
10
// Initialize View Model
11
viewModel
=
WeekViewViewModel
(
weatherData
:
weatherData
.
dailyData
)
12
}
The unit tests for the WeekViewViewModel
struct are very easy to write. The simplest unit test of this book is the one for the numberOfSections
computed property since it always returns the value 1
.
WeekViewViewModelTests.swift
1
//
MARK:
- Tests for Number of Sections
2
3
func
testNumberOfSections
()
{
4
XCTAssertEqual
(
viewModel
.
numberOfSections
,
1
)
5
}
The unit test for numberOfDays
is also easy to write. One assertion is all we need.
WeekViewViewModelTests.swift
1
//
MARK:
- Tests for Number of Days
2
3
func
testNumberOfDays
()
{
4
XCTAssertEqual
(
viewModel
.
numberOfDays
,
8
)
5
}
Testing the viewModel(for:)
method is slightly more complicated. We can take a few approaches. Remember that this method returns an object that conforms to the WeatherDayRepresentable
protocol. One approach is to ask the view model for the object that corresponds with a particular index and assert that the day
and date
properties are equal to the values we expect based on the stub we added to the unit testing bundle.
WeekViewViewModelTests.swift
1
//
MARK:
- Tests for View Model for Index
2
3
func
testViewModelForIndex
()
{
4
let
weatherDayViewViewModel
=
viewModel
.
viewModel
(
for
:
5
)
5
6
XCTAssertEqual
(
weatherDayViewViewModel
.
day
,
"Saturday"
)
7
XCTAssertEqual
(
weatherDayViewViewModel
.
date
,
"July 15"
)
8
}
These are all the unit tests we need to write for the WeekViewViewModel
struct. Press Command + U to run the test suite.
You should now be able to write the unit tests for the WeatherDayViewViewModel
struct. The unit tests are very similar to those of the DayViewViewModel
struct. The only difficulty is instantiating the view model. Give it a try to see if you can make it work. You can find the solution below.
We create a new file and name it WeatherDayViewViewModelTests.swift.
We add an import statement for the Cloudy module and define a property for the view model of type WeatherDayViewViewModel!
, an implicitly unwrapped optional.
WeatherDayViewViewModelTests.swift
1
import
XCTest
2
@
testable
import
Cloudy
3
4
class
WeatherDayViewViewModelTests
:
XCTestCase
{
5
6
//
MARK:
- Properties
7
8
var
viewModel
:
WeatherDayViewViewModel
!
9
10
//
MARK:
- Set Up & Tear Down
11
12
override
func
setUp
()
{
13
super
.
setUp
()
14
}
15
16
override
func
tearDown
()
{
17
super
.
tearDown
()
18
}
19
20
}
We instantiate the view model in the setUp()
method. We load the stub from the unit testing bundle, create a WeatherData
instance with it, and use the WeatherData
instance to initialize the view model. Because we need a WeatherDayData
instance to initialize the view model, we ask the WeatherData
instance for one. This is the only complexity of the unit tests for the WeatherDayViewViewModel
struct.
WeatherDayViewViewModelTests.swift
1
override
func
setUp
()
{
2
super
.
setUp
()
3
4
// Load Stub
5
let
data
=
loadStubFromBundle
(
withName
:
"darksky"
,
extension
:
"j\
6
son"
)
7
let
weatherData
:
WeatherData
=
try
!
JSONDecoder
.
decode
(
data
:
dat
\
8
a
)
9
10
// Initialize View Model
11
viewModel
=
WeatherDayViewViewModel
(
weatherDayData
:
weatherData
.
\
12
dailyData
[
5
])
13
}
The unit tests should look familiar. They’re similar to the ones we wrote for the DayViewViewModel
struct.
WeatherDayViewViewModelTests.swift
1
//
MARK:
- Tests for Day
2
3
func
testDay
()
{
4
XCTAssertEqual
(
viewModel
.
day
,
"Saturday"
)
5
}
6
7
//
MARK:
- Tests for Date
8
9
func
testDate
()
{
10
XCTAssertEqual
(
viewModel
.
date
,
"July 15"
)
11
}
12
13
//
MARK:
- Tests for Temperature
14
15
func
testTemperature_Fahrenheit
()
{
16
let
temperatureNotation
:
TemperatureNotation
=
.
fahrenheit
17
UserDefaults
.
standard
.
set
(
temperatureNotation
.
rawValue
,
forKey
:
\
18
UserDefaultsKeys
.
temperatureNotation
)
19
20
XCTAssertEqual
(
viewModel
.
temperature
,
"37 째F - 47 째F"
)
21
}
22
23
func
testTemperature_Celsius
()
{
24
let
temperatureNotation
:
TemperatureNotation
=
.
celsius
25
UserDefaults
.
standard
.
set
(
temperatureNotation
.
rawValue
,
forKey
:
\
26
UserDefaultsKeys
.
temperatureNotation
)
27
28
XCTAssertEqual
(
viewModel
.
temperature
,
"3 째C - 8 째C"
)
29
}
30
31
//
MARK:
- Tests for Wind Speed
32
33
func
testWindSpeed_Imperial
()
{
34
let
unitsNotation
:
UnitsNotation
=
.
imperial
35
UserDefaults
.
standard
.
set
(
unitsNotation
.
rawValue
,
forKey
:
UserDe
\
36
faultsKeys
.
unitsNotation
)
37
38
XCTAssertEqual
(
viewModel
.
windSpeed
,
"1 MPH"
)
39
}
40
41
func
testWindSpeed_Metric
()
{
42
let
unitsNotation
:
UnitsNotation
=
.
metric
43
UserDefaults
.
standard
.
set
(
unitsNotation
.
rawValue
,
forKey
:
UserDe
\
44
faultsKeys
.
unitsNotation
)
45
46
XCTAssertEqual
(
viewModel
.
windSpeed
,
"2 KPH"
)
47
}
48
49
//
MARK:
- Tests for Image
50
51
func
testImage
()
{
52
let
viewModelImage
=
viewModel
.
image
53
let
imageDataViewModel
=
UIImagePNGRepresentation
(
viewModelImage
\
54
!
)
!
55
let
imageDataReference
=
UIImagePNGRepresentation
(
UIImage
(
named
:
\
56
"cloudy"
)
!
)
!
57
58
XCTAssertNotNil
(
viewModelImage
)
59
XCTAssertEqual
(
viewModelImage
!.
size
.
width
,
236.0
)
60
XCTAssertEqual
(
viewModelImage
!.
size
.
height
,
172.0
)
61
XCTAssertEqual
(
imageDataViewModel
,
imageDataReference
)
62
}
In the tearDown()
method, we reset the state we set in the unit tests.
WeatherDayViewViewModelTests.swift
1
override
func
tearDown
()
{
2
super
.
tearDown
()
3
4
// Reset User Defaults
5
UserDefaults
.
standard
.
removeObject
(
forKey
:
UserDefaultsKeys
.
unit
\
6
sNotation
)
7
UserDefaults
.
standard
.
removeObject
(
forKey
:
UserDefaultsKeys
.
temp
\
8
eratureNotation
)
9
}
Well done. We’ve now fully covered the view models with unit tests. Run the test suite one more time to make sure all the unit tests pass.
In the second part of this book, we take the Model-View-ViewModel pattern to the next level by introducing bindings.