Skip to content

Commit a7f9a6c

Browse files
authored
chore: separate unit/integration tests, run on PR (#119)
* chore: separate unit/integration tests, run on PR
1 parent 4d6b725 commit a7f9a6c

File tree

5 files changed

+169
-33
lines changed

5 files changed

+169
-33
lines changed

.github/workflows/unittests.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Run Unit Tests
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
push:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
name: Unit Tests
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Java
19+
uses: actions/setup-java@v4
20+
with:
21+
distribution: 'temurin'
22+
java-version: '17'
23+
- name: Configure Maven for GitHub Packages
24+
run: |
25+
mkdir -p ~/.m2
26+
cat > ~/.m2/settings.xml <<EOF
27+
<settings>
28+
<servers>
29+
<server>
30+
<id>github</id>
31+
<username>${{ github.actor }}</username>
32+
<password>${{ secrets.GITHUB_TOKEN }}</password>
33+
</server>
34+
</servers>
35+
</settings>
36+
EOF
37+
- name: Run Unit Tests
38+
run: mvn clean test

pom.xml

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>com.bigboxer23</groupId>
66
<artifactId>switchbotapi-java</artifactId>
7-
<version>1.2.1</version>
7+
<version>1.2.2</version>
88

99
<name>switchbotapi-java</name>
1010
<url>https://github.com/bigboxer23/switchbotapi-java</url>
@@ -22,6 +22,12 @@
2222
</properties>
2323

2424
<dependencies>
25+
<dependency>
26+
<groupId>org.mockito</groupId>
27+
<artifactId>mockito-core</artifactId>
28+
<version>5.17.0</version>
29+
<scope>test</scope>
30+
</dependency>
2531
<dependency>
2632
<groupId>org.junit.jupiter</groupId>
2733
<artifactId>junit-jupiter-api</artifactId>
@@ -78,9 +84,56 @@
7884
<release>17</release>
7985
</configuration>
8086
</plugin>
87+
<plugin>
88+
<groupId>org.apache.maven.plugins</groupId>
89+
<artifactId>maven-failsafe-plugin</artifactId>
90+
<version>3.5.3</version>
91+
<configuration>
92+
<includes>
93+
<include>**/*IT.java</include>
94+
<include>**/*ITCase.java</include>
95+
<include>**/*IntegrationTest.java</include>
96+
</includes>
97+
<parallel>methods</parallel>
98+
<threadCount>4</threadCount>
99+
<useUnlimitedThreads>false</useUnlimitedThreads>
100+
<skipITs>${!integration}</skipITs>
101+
<rerunFailingTestsCount>1</rerunFailingTestsCount>
102+
</configuration>
103+
<executions>
104+
<execution>
105+
<id>integration-tests</id>
106+
<goals>
107+
<goal>integration-test</goal>
108+
</goals>
109+
<phase>integration-test</phase>
110+
</execution>
111+
<execution>
112+
<id>verify</id>
113+
<goals>
114+
<goal>verify</goal>
115+
</goals>
116+
<phase>verify</phase>
117+
</execution>
118+
</executions>
119+
</plugin>
81120
<plugin>
82121
<artifactId>maven-surefire-plugin</artifactId>
83122
<version>3.5.3</version>
123+
<configuration>
124+
<includes>
125+
<include>**/*Test.java</include>
126+
<include>**/*Tests.java</include>
127+
</includes>
128+
<excludes>
129+
<exclude>**/*IT.java</exclude>
130+
<exclude>**/*ITCase.java</exclude>
131+
<exclude>**/*IntegrationTest.java</exclude>
132+
</excludes>
133+
<parallel>methods</parallel>
134+
<threadCount>4</threadCount>
135+
<useUnlimitedThreads>false</useUnlimitedThreads>
136+
</configuration>
84137
</plugin>
85138
<plugin>
86139
<artifactId>maven-jar-plugin</artifactId>

src/main/java/com/bigboxer23/switch_bot/SwitchBotDeviceApi.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ public class SwitchBotDeviceApi {
2222

2323
private Map<String, String> deviceIdToNames;
2424

25-
private long deviceIdToNamesCacheTime = -1;
25+
protected long deviceIdToNamesCacheTime = -1;
2626

2727
protected SwitchBotDeviceApi(SwitchBotApi provider) {
2828
this.provider = provider;
29-
refreshDeviceNameMap();
3029
}
3130

3231
public String getDeviceNameFromId(String deviceId) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.bigboxer23.switch_bot;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
import static org.mockito.Mockito.*;
5+
6+
import com.bigboxer23.switch_bot.data.Device;
7+
import com.bigboxer23.switch_bot.data.DeviceCommand;
8+
import com.bigboxer23.utils.time.ITimeConstants;
9+
import java.io.IOException;
10+
import java.util.List;
11+
import org.junit.jupiter.api.Test;
12+
13+
public class SwitchBotApiUnitTest {
14+
@Test
15+
public void testSingletonBehavior() {
16+
SwitchBotApi mockApi1 = SwitchBotApi.getInstance("token", "secret");
17+
SwitchBotApi mockApi2 = SwitchBotApi.getInstance("token", "secret");
18+
assertSame(mockApi1, mockApi2, "Should return the same singleton instance");
19+
}
20+
21+
@Test
22+
public void testDeviceCommandSerialization() throws IOException {
23+
SwitchBotApi api = SwitchBotApi.getInstance("token", "secret");
24+
25+
DeviceCommand originalCommand = new DeviceCommand("turnOn", "default");
26+
27+
String json = api.getMoshi().adapter(DeviceCommand.class).toJson(originalCommand);
28+
assertNotNull(json);
29+
assertTrue(json.contains("turnOn"));
30+
31+
DeviceCommand deserializedCommand =
32+
api.getMoshi().adapter(DeviceCommand.class).fromJson(json);
33+
assertNotNull(deserializedCommand);
34+
assertEquals(originalCommand.getCommand(), deserializedCommand.getCommand());
35+
assertEquals(originalCommand.getParameter(), deserializedCommand.getParameter());
36+
assertEquals("command", deserializedCommand.getCommandType());
37+
}
38+
39+
@Test
40+
public void testDeviceNameCacheRefreshBehavior() throws IOException {
41+
SwitchBotApi mockApi = mock(SwitchBotApi.class);
42+
SwitchBotDeviceApi deviceApi = spy(new SwitchBotDeviceApi(mockApi));
43+
44+
deviceApi.deviceIdToNamesCacheTime = System.currentTimeMillis() - (ITimeConstants.HOUR * 2);
45+
46+
Device dummyDevice = new Device();
47+
dummyDevice.setDeviceId("12345");
48+
dummyDevice.setDeviceName("Test Device");
49+
doReturn(List.of(dummyDevice)).when(deviceApi).getDevices();
50+
51+
String deviceName = deviceApi.getDeviceNameFromId("12345");
52+
assertEquals("Test Device", deviceName);
53+
54+
reset(deviceApi);
55+
deviceApi.getDeviceNameFromId("12345");
56+
verify(deviceApi, times(0)).getDevices();
57+
}
58+
}

src/test/java/com/bigboxer23/switch_bot/SwitchBotApiTest.java renamed to src/test/java/com/bigboxer23/switch_bot/integration/SwitchBotApiIntegrationTest.java

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
package com.bigboxer23.switch_bot;
1+
package com.bigboxer23.switch_bot.integration;
22

33
import static org.junit.jupiter.api.Assertions.*;
44

5+
import com.bigboxer23.switch_bot.IDeviceCommands;
6+
import com.bigboxer23.switch_bot.IDeviceTypes;
7+
import com.bigboxer23.switch_bot.SwitchBotApi;
58
import com.bigboxer23.switch_bot.data.Device;
69
import com.bigboxer23.utils.command.Command;
710
import com.bigboxer23.utils.properties.PropertyUtils;
811
import java.io.IOException;
912
import java.util.List;
13+
import org.junit.jupiter.api.Tag;
1014
import org.junit.jupiter.api.Test;
1115

12-
/** Need to define environment variables for SwitchBotToken/SwitchBotSecret to run tests */
13-
public class SwitchBotApiTest {
16+
@Tag("integration")
17+
public class SwitchBotApiIntegrationTest {
1418
private static final String token = PropertyUtils.getProperty("switchbot_token");
15-
1619
private static final String secret = PropertyUtils.getProperty("switchbot_secret");
17-
1820
private static final SwitchBotApi instance = SwitchBotApi.getInstance(token, secret);
1921

2022
@Test
2123
public void testGetDevices() throws IOException {
2224
List<Device> devices = instance.getDeviceApi().getDevices();
23-
assertFalse(devices.isEmpty());
25+
assertFalse(devices.isEmpty(), "Device list should not be empty");
2426
assertNotNull(devices.get(0).getDeviceId());
2527
}
2628

@@ -37,9 +39,8 @@ public void getDeviceNameFromId() throws IOException {
3739
public void testDeviceStatus() throws IOException {
3840
try {
3941
instance.getDeviceApi().getDeviceStatus("123");
40-
fail();
41-
} catch (IOException e) {
42-
42+
fail("Expected IOException for invalid device ID");
43+
} catch (IOException ignored) {
4344
}
4445
for (Device device : instance.getDeviceApi().getDevices()) {
4546
assertNotNull(device.getDeviceId());
@@ -55,17 +56,16 @@ public void testDeviceStatus() throws IOException {
5556
}
5657
case IDeviceTypes.CURTAIN -> {
5758
assertTrue(status.getSlidePosition() >= 0);
58-
assertTrue(status.isMoving());
5959
assertTrue(status.getBattery() >= 0);
6060
}
6161
case IDeviceTypes.PLUG_MINI -> {
6262
assertTrue("on".equals(status.getPower()) || "off".equals(status.getPower()));
63-
assertTrue(status.getVoltage() > 0);
63+
assertTrue(status.getVoltage() > -1);
6464
assertTrue(status.getWatts() > -1);
6565
assertTrue(status.getElectricityOfDay() >= 0);
6666
assertTrue(status.getElectricCurrent() > -1);
6767
}
68-
case IDeviceTypes.WATER_DETECTOR -> assertTrue(status.isWet());
68+
case IDeviceTypes.WATER_DETECTOR -> assertTrue(status.isWet() || status.isDry());
6969
case IDeviceTypes.METER_PRO_CO2 -> assertTrue(status.getCo2() > 0);
7070
}
7171
}
@@ -80,25 +80,14 @@ public void testCurtainDeviceCommands() throws IOException, InterruptedException
8080
.orElse(null);
8181
assertNotNull(curtain);
8282
assertNotNull(curtain.getDeviceId());
83-
System.out.println("slide position "
84-
+ instance.getDeviceApi().getDeviceStatus(curtain.getDeviceId()).getSlidePosition());
8583

8684
instance.getDeviceApi().sendDeviceControlCommands(curtain.getDeviceId(), IDeviceCommands.CURTAIN_CLOSE);
87-
pollForStatus(() -> {
88-
int slidePosition = instance.getDeviceApi()
89-
.getDeviceStatus(curtain.getDeviceId())
90-
.getSlidePosition();
91-
System.out.println("slide position " + slidePosition);
92-
return slidePosition >= 90;
93-
});
85+
pollForStatus(() ->
86+
instance.getDeviceApi().getDeviceStatus(curtain.getDeviceId()).getSlidePosition() >= 90);
87+
9488
instance.getDeviceApi().sendDeviceControlCommands(curtain.getDeviceId(), IDeviceCommands.CURTAIN_OPEN);
95-
pollForStatus(() -> {
96-
int slidePosition = instance.getDeviceApi()
97-
.getDeviceStatus(curtain.getDeviceId())
98-
.getSlidePosition();
99-
System.out.println("slide position " + slidePosition);
100-
return slidePosition == 0;
101-
});
89+
pollForStatus(() ->
90+
instance.getDeviceApi().getDeviceStatus(curtain.getDeviceId()).getSlidePosition() == 0);
10291
}
10392

10493
@Test
@@ -120,8 +109,7 @@ public void testPlugDeviceCommands() throws IOException, InterruptedException {
120109

121110
private void pollForStatus(Command<Boolean> command) throws IOException, InterruptedException {
122111
boolean result = command.execute();
123-
for (int ai = 0; ai < 10 && !result; ai++) {
124-
System.out.println("sleeping " + ai);
112+
for (int i = 0; i < 10 && !result; i++) {
125113
Thread.sleep(2000);
126114
result = command.execute();
127115
}

0 commit comments

Comments
 (0)